npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@koltakov/ffa-core

v0.16.1

Published

Instant mock REST API for frontend development

Readme

ffa-core

Frontend First API — instant mock REST API for frontend development.

Stop waiting for the backend. Describe your data in one config file, run one command, get a fully working REST API.

npx @koltakov/ffa-core init
npx @koltakov/ffa-core dev

Why ffa?

  • Zero boilerplate — one config file, one command
  • Auto CRUD — 6 routes per entity, out of the box
  • Smart fake data — field names drive generation (email, price, avatar, ...)
  • Typed DSLstring().email().required() with full TypeScript autocomplete
  • Zod validation — POST/PUT/PATCH bodies validated against your schema
  • RelationsbelongsTo / hasMany with inline join via ?include=
  • Swagger UI — auto-generated docs at /docs
  • Delay simulation — test loading states with artificial latency
  • TypeScript or JSON — pick your config format

Quick Start

npm install @koltakov/ffa-core

npx ffa init   # scaffold ffa.config.ts
npx ffa dev    # start server

Config

// ffa.config.ts
import {
  defineConfig, entity,
  string, number, boolean, enumField,
  belongsTo, hasMany, object, array,
} from '@koltakov/ffa-core'

export default defineConfig({
  server: {
    port: 3333,
    delay: [200, 600],   // artificial latency in ms (number or [min, max])
    persist: true,       // save data to ffa-data.json between restarts
    errorRate: 0.05,     // 5% of requests return 500 (for error state testing)
  },

  entities: {
    User: entity({
      name:   string().fullName().required(),
      email:  string().email().required(),
      role:   enumField(['admin', 'editor', 'viewer']).required(),
      avatar: string().avatar().optional(),
      address: object({
        city:    string().city(),
        country: string().country(),
        zip:     string().zip(),
      }),
      phones: array(string().phone()),
    }, {
      count: 20,
      // Guaranteed records — appear first, rest are generated up to count
      seed: [
        { name: 'Admin User', email: '[email protected]', role: 'admin' },
      ],
    }),

    Post: entity({
      title:    string().sentence().required(),
      body:     string().paragraph().required(),
      status:   enumField(['draft', 'published', 'archived']).required(),
      price:    number().price().required(),
      rating:   number().rating().optional(),
      authorId: belongsTo('User'),
      tagIds:   hasMany('Tag'),
    }, {
      count: 50,
      meta: {
        currency: 'USD',                                           // static value
        total:    (items) => items.length,                         // computed from filtered items
        avgPrice: (items) => +(
          items.reduce((s, i) => s + (i.price as number), 0) / items.length
        ).toFixed(2),
      },
    }),

    Tag: entity({
      name:  string().word().required(),
      color: string().hexColor().optional(),
    }, { count: 15 }),
  },
})

CLI

| Command | Description | |---------|-------------| | ffa dev | Start the dev server | | ffa dev -p 4000 | Override port | | ffa dev -w | Watch mode — restart on config changes | | ffa dev -o | Open Swagger UI in browser on start | | ffa init | Scaffold ffa.config.ts in current directory | | ffa inspect | Show config structure without starting the server | | ffa snapshot | Export current in-memory DB to JSON (server must be running) | | ffa snapshot -o dump.json | Custom output path | | ffa reset | Delete ffa-data.json (clears persisted data) |


Generated Endpoints

For every entity Foo ffa creates six routes:

| Method | Path | Description | Status | |--------|------|-------------|--------| | GET | /foos | List all records | 200 | | GET | /foos/:id | Get single record | 200, 404 | | POST | /foos | Create a record | 201, 422 | | PUT | /foos/:id | Full replace | 200, 404, 422 | | PATCH | /foos/:id | Partial update | 200, 404, 422 | | DELETE | /foos/:id | Delete a record | 200, 404 |

Names are auto-pluralized: Product/products, Category/categories.

System endpoints

| Method | Path | Description | |--------|------|-------------| | POST | /__reset | Regenerate all data | | GET | /__snapshot | Dump current DB to JSON | | GET | /docs | Swagger UI | | GET | /openapi.json | OpenAPI 3.0 spec |


Query Params (GET /list)

Pagination, sorting, search

?page=2&limit=10
?sort=price&order=desc
?search=apple                 full-text search across all string fields
?status=published             exact match filter

Filter operators

?price_gte=100                price >= 100
?price_lte=500                price <= 500
?price_gt=0                   price > 0
?price_lt=1000                price < 1000
?price_ne=0                   price != 0
?status_in=draft,published    status in ['draft', 'published']
?title_contains=hello         title contains 'hello' (case-insensitive)

All params are combinable:

GET /posts?search=react&status_in=draft,published&price_gte=10&sort=price&order=asc&page=1&limit=20

Response envelope

{
  "data": [{ "id": "...", "title": "...", "price": 49.99 }],
  "meta": {
    "pagination": { "total": 47, "page": 1, "limit": 10, "pages": 5 },
    "currency": "USD",
    "avgPrice": 124.50
  }
}

Header: X-Total-Count: 47


Field Types

Primitives

string()     // chainable hints — see below
number()     // chainable hints — see below
boolean()
uuid()
datetime()

Enum

enumField(['draft', 'published', 'archived'])

Relations

belongsTo('User')   // stores a random User id
hasMany('Tag')      // stores an array of 1–3 Tag ids

Inline join on GET /:id:

GET /posts/abc?include=authorId,tagIds

Nested structures (v0.12.0)

// Nested object — generates each field recursively
address: object({
  city:    string().city(),
  country: string().country(),
  zip:     string().zip(),
})

// Array of typed items
tags:   array(string().word())
phones: array(string().phone(), [1, 4])  // [min, max] items
scores: array(number().rating())

String Fake Hints

Chain a method on string() to control what gets generated:

// Internet
string().email()       // "[email protected]"
string().url()         // "https://example.com"
string().domain()      // "example.com"
string().ip()          // "192.168.1.1"
string().username()    // "john_doe"

// Media
string().image()              // "https://picsum.photos/seed/xxx/1280/720" (default 16:9)
string().image(300, 450)      // "https://picsum.photos/seed/xxx/300/450"  (portrait 2:3)
string().image(1920, 1080)    // "https://picsum.photos/seed/xxx/1920/1080" (Full HD)
string().avatar()             // avatar URL

// Person
string().firstName()   // "Alice"
string().lastName()    // "Johnson"
string().fullName()    // "Alice Johnson"
string().phone()       // "+1-555-234-5678"

// Location
string().city()        // "Berlin"
string().country()     // "Germany"
string().address()     // "12 Oak Street"
string().zip()         // "10115"
string().locale()      // "DE"

// Business
string().company()     // "Acme Corp"
string().jobTitle()    // "Senior Engineer"
string().department()  // "Electronics"
string().currency()    // "EUR"

// Text
string().word()        // "matrix"
string().slug()        // "hello-world"
string().sentence()    // "The quick brown fox."
string().paragraph()   // "Lorem ipsum dolor..."
string().bio()         // "Lorem ipsum dolor..."

// Visual
string().color()       // "azure"
string().hexColor()    // "#A3F5C2"

// Id
string().uuid()        // UUID v4

Number Fake Hints

number().price()       // 19.99
number().age()         // 34
number().rating()      // 4
number().percent()     // 72
number().lat()         // 51.5074
number().lng()         // -0.1278
number().year()        // 2021

Smart Field-Name Detection

When no hint is set, ffa infers the right value from the field name automatically:

| Field name pattern | Generated value | |--------------------|----------------| | email, mail | Email address | | name, firstName | First name | | lastName, surname | Last name | | phone, tel, mobile | Phone number | | city, country, address | Location values | | url, website, link | URL | | avatar, photo | Avatar URL | | image | Image URL | | company | Company name | | title, heading | Short sentence | | description, bio, text | Paragraph | | price, cost, amount | Price | | color | Color name |


Field Rules

All builders share these chainable rules:

| Method | Effect | |--------|--------| | .required() | Field required in POST/PUT | | .optional() | Field can be omitted (default) | | .min(n) | Min string length / min number value | | .max(n) | Max string length / max number value | | .readonly() | Excluded from create/update validation | | .default(val) | Default value | | .fake(hint) | Explicit faker hint | | .image(w, h) | Image dimensions (only for string().image()) |


Seed Data (v0.9.0)

Guarantee specific records always exist in your entity:

User: entity({
  name: string().fullName().required(),
  role: enumField(['admin', 'editor', 'viewer']).required(),
}, {
  count: 20,
  seed: [
    { id: 'admin-1', name: 'Admin User', role: 'admin' },
    { name: 'Guest User', role: 'viewer' },  // id auto-generated if omitted
  ],
})

Seed records appear first. The remaining count - seed.length records are generated.


Custom Meta (v0.8.0)

Attach extra fields to the list response meta. Values can be static or computed from the current filtered dataset (before pagination):

Product: entity({ price: number().price(), status: enumField(['active', 'sale']) }, {
  count: 50,
  meta: {
    currency:   'USD',                                              // static
    apiVersion: 2,                                                  // static
    total:      (items) => items.length,                            // computed
    avgPrice:   (items) => +(items.reduce((s, i) => s + (i.price as number), 0) / items.length).toFixed(2),
    byStatus:   (items) => Object.fromEntries(
      ['active', 'sale'].map(s => [s, items.filter(i => i.status === s).length])
    ),
  },
})

Meta functions receive items after search/filter but before pagination — so aggregates always reflect the current query.


Seed Data Export (v0.15.0)

Export the current in-memory DB while the server is running — useful for generating fixture files:

ffa snapshot                  # → ffa-snapshot.json
ffa snapshot -o fixtures.json
ffa snapshot -p 4000          # specify port if not in config

Or call directly:

GET /__snapshot   → { "User": [...], "Post": [...] }

Inspect Config (v0.16.0)

Print your config structure without starting the server:

ffa inspect
  FFA inspect  port 3333

  User  20 records  +1 seed
    *  name      string  [fullName]
    *  email     string  [email]
    *  role      enum    (admin | editor | viewer)
       avatar    string  [avatar]
       address   object  { city, country, zip }
       phones    array   of string

  Post  50 records
    *  title     string     [sentence]
    *  body      string     [paragraph]
    *  status    enum       (draft | published | archived)
    *  price     number     [price]
       authorId  belongsTo  → User
       tagIds    hasMany    → Tag
       meta: currency, total, avgPrice

  delay 200–600ms  ·  persist ffa-data.json  ·  errorRate 5%

Config Validation (v0.11.0)

On startup, ffa checks your config and prints warnings:

  ⚠  Post.authorId (belongsTo) references 'Author' which is not defined
  ⚠  Tag.type is enum but has no values
  ⚠  User: seed has 5 records but count is 3 — seed will be truncated

Error Simulation (v0.14.0)

Test your frontend error handling without mocking:

server: {
  errorRate: 0.1   // 10% of requests return 500
}

System routes (/__reset, /__snapshot, /docs) are never affected.


Persistence

server: {
  persist: true,             // save to ./ffa-data.json
  persist: './data/db.json', // custom path
}

Data is saved on every write (POST/PUT/PATCH/DELETE) and restored on restart.


JSON Config (no TypeScript)

{
  "server": { "port": 3333, "delay": 300 },
  "entities": {
    "Product": {
      "count": 20,
      "fields": {
        "title":     "string!",
        "price":     "number",
        "status":    ["draft", "published", "archived"],
        "inStock":   "boolean",
        "createdAt": "datetime"
      }
    }
  }
}

Save as ffa.config.json. "string!" = required, "string" = optional. Arrays are shorthand for enumField.


Terminal Output

  FFA dev server  v0.16.0

  Entity    Count  Route
  ──────────────────────────────────────────────────────────
  User         20  GET POST  http://localhost:3333/users
  Post         50  GET POST  http://localhost:3333/posts
  Tag          15  GET POST  http://localhost:3333/tags

  delay      200–600ms
  errorRate  5% chance of 500

  docs    http://localhost:3333/docs
  reset   POST http://localhost:3333/__reset

  GET    /users                              200  14ms
  POST   /posts                             201  312ms
  GET    /posts/abc-123                     404  8ms
  PATCH  /posts/def-456                     200  267ms

Testing

npm test          # run all tests once
npm run test:watch  # watch mode

Tests live in tests/ and are excluded from the build. 144 tests covering:

  • DSL builders and all fake hints (string(), number(), object(), array(), ...)
  • Zod schema generation for all field types
  • entity() helper
  • createMemoryDB — CRUD, pagination, sorting, search, filter operators, seed, meta, relations, image dimensions
  • validateConfig — all warning scenarios

License

ISC