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

schema-driven-flow

v1.0.0

Published

Define schema once → Mongoose, TypeScript, validation, React forms, API client. Full-stack schema engine for MERN.

Readme

schema-flow

Define schema ONCE → everything else auto-generated

A full-stack schema engine for MERN. One source of truth for Mongoose, validation, TypeScript, and Express.


Table of Contents


What's Included

| Feature | Status | Description | |---------|--------|-------------| | defineSchema | ✅ | Single source of truth for your data shape | | validate | ✅ | Validate data in Node.js or browser (no DB needed) | | toMongooseSchema | ✅ | Get raw Mongoose schema (for custom use) | | createMongooseModel | ✅ | Get a ready-to-use Mongoose model | | validateRequestBody | ✅ | Express middleware to validate req.body | | validateQuery | ✅ | Express middleware to validate req.query | | validateParams | ✅ | Express middleware to validate req.params | | validateBody | ✅ | Generic validation for any req key | | createCRUD | ✅ | Full REST API: GET, POST, PUT, DELETE | | toTypeScript | ✅ | Generate TypeScript interface string | | createAPIClient | ✅ | Type-safe API client for frontend (getAll, getById, create, update, delete) | | useSchemaForm | ✅ | React hook for form state + validation (schema-flow/react) | | generateTypes | ✅ | Regenerate TypeScript types to file | | watchAndRegenerate | ✅ | Watch schemas and auto-regenerate types | | CLI | ✅ | schema-flow init, generate, watch |


Install

# Core (always needed)
npm install schema-flow

# For Mongoose model & CRUD (backend with MongoDB)
npm install mongoose

# For Express middleware & createCRUD
npm install express

# For React form hook (schema-flow/react)
npm install react

1. defineSchema — Define Your Schema

Purpose: Create a single schema that drives validation, Mongoose, TypeScript, and more.

Basic usage

const { defineSchema } = require('schema-flow');

const User = defineSchema({
  name: 'User',        // Required: used for Mongoose model name, TypeScript interface
  fields: {
    name:   { type: 'string', required: true, minLength: 2 },
    email:  { type: 'email', required: true },
    age:    { type: 'number', min: 0, max: 150 },
  },
  timestamps: true,    // Optional, default: true. Adds createdAt, updatedAt in Mongoose
});

What you get

  • A Schema instance with name, fields, and timestamps
  • Reusable for validate(), createMongooseModel(), toTypeScript(), createCRUD(), etc.

Schema config reference

| Key | Type | Required | Default | Description | |-----|------|----------|---------|-------------| | name | string | ✅ | — | Model/interface name (e.g. 'User') | | fields | object | ✅ | — | Field definitions (see below) | | timestamps | boolean | ❌ | true | Add Mongoose timestamps |


2. validate — Validate Data Anywhere

Purpose: Check data against your schema. Works in Node.js and browser. No database or Express needed.

Basic usage

const { defineSchema, validate } = require('schema-flow');

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true },
    email: { type: 'email', required: true },
  },
});

// Valid data
const result1 = validate(User, { name: 'John', email: '[email protected]' });
console.log(result1.valid);   // true
console.log(result1.data);    // { name: 'John', email: '[email protected]' }
console.log(result1.errors);  // []

// Invalid data
const result2 = validate(User, { name: '', email: 'not-an-email' });
console.log(result2.valid);   // false
console.log(result2.data);    // undefined
console.log(result2.errors);  // [{ path: 'name', message: '...' }, ...]

Return value: ValidationResult

| Property | Type | Description | |----------|------|-------------| | valid | boolean | true if validation passed | | errors | ValidationError[] | List of validation errors | | data | Record<string, unknown> | undefined | Validated data with defaults applied. Only set when valid === true |

ValidationError shape

| Property | Type | Description | |----------|------|-------------| | path | string | Field path (e.g. 'email', 'address.city') | | message | string | Error message | | value | unknown | The invalid value |

Defaults

If a field has default and the value is undefined or null, the default is applied in result.data:

const Schema = defineSchema({
  name: 'Item',
  fields: {
    status: { type: 'string', default: 'draft' },
  },
});

const r = validate(Schema, {});
// r.valid === true
// r.data === { status: 'draft' }

3. Mongoose — toMongooseSchema & createMongooseModel

Purpose: Turn your schema-flow schema into a Mongoose schema or model. Requires mongoose to be installed and (for models) a DB connection.

toMongooseSchema

Returns a raw Mongoose schema. Use when you need to customize or compose schemas.

const { defineSchema, toMongooseSchema } = require('schema-flow');
const mongoose = require('mongoose');

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true },
    email: { type: 'email', required: true },
  },
});

const mongooseSchema = toMongooseSchema(User);
// Add plugins, virtuals, etc.
mongooseSchema.plugin(somePlugin);
const UserModel = mongoose.model('User', mongooseSchema);

createMongooseModel

Returns a ready-to-use Mongoose model. Connect to MongoDB first.

const { defineSchema, createMongooseModel } = require('schema-flow');
const mongoose = require('mongoose');

await mongoose.connect('mongodb://localhost/mydb');

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true },
    email: { type: 'email', required: true },
  },
});

const UserModel = createMongooseModel(User);

// Use like any Mongoose model
await UserModel.create({ name: 'Jane', email: '[email protected]' });
const users = await UserModel.find();

Using a separate MongoDB connection

const conn = mongoose.createConnection('mongodb://other-host/mydb');
const UserModel = createMongooseModel(User, conn);

4. Express — Validation Middleware

Purpose: Validate req.body, req.query, or req.params before your route handler runs. Requires express.

validateRequestBody

Validates req.body. Use for POST/PUT/PATCH.

const express = require('express');
const { defineSchema, validateRequestBody } = require('schema-flow');

const app = express();
app.use(express.json());

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true },
    email: { type: 'email', required: true },
  },
});

app.post('/users', validateRequestBody(User), (req, res) => {
  // req.validated contains validated, type-safe data
  const { name, email } = req.validated;
  // ... create user
});

On validation failure, the middleware sends 400 with:

{
  "error": "Validation failed",
  "details": [
    { "path": "email", "message": "email must be a valid email", "value": "bad" }
  ]
}

validateQuery

Validates req.query (query string params).

const Pagination = defineSchema({
  name: 'Pagination',
  fields: {
    page:   { type: 'number', default: 1 },
    limit:  { type: 'number', default: 10, max: 100 },
  },
});

app.get('/users', validateQuery(Pagination), (req, res) => {
  const { page, limit } = req.validated;
  // ...
});

validateParams

Validates req.params (URL params).

const IdParam = defineSchema({
  name: 'IdParam',
  fields: {
    id: { type: 'objectId', required: true },
  },
});

app.get('/users/:id', validateParams(IdParam), (req, res) => {
  const { id } = req.validated;
  // ...
});

validateBody (generic)

Validate any key on req:

const { validateBody } = require('schema-flow');

// Same as validateRequestBody
validateBody(User, 'body');

// Same as validateQuery
validateBody(Pagination, 'query');

// Same as validateParams
validateBody(IdParam, 'params');

5. createCRUD — Auto REST API

Purpose: Generate a full REST API (list, get one, create, update, delete) for a schema. Requires express and mongoose, and MongoDB must be connected.

Basic usage

const express = require('express');
const mongoose = require('mongoose');
const { defineSchema, createCRUD } = require('schema-flow');

await mongoose.connect('mongodb://localhost/mydb');

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true },
    email: { type: 'email', required: true },
  },
});

const app = express();
app.use(express.json());

// Mount CRUD at /api/users
app.use('/api/users', createCRUD(User));

Endpoints created

| Method | Path | Description | |--------|------|-------------| | GET | /api/users | List all users | | GET | /api/users/:id | Get one user by ID | | POST | /api/users | Create user (body validated) | | PUT | /api/users/:id | Update user (body validated) | | DELETE | /api/users/:id | Delete user |

CRUD options

createCRUD(User, {
  basePath: '/users',           // Not used when mounting; you control path via app.use
  connection: mongooseConnection,  // Optional: use a specific MongoDB connection
});

The path is determined by where you mount the router:

app.use('/api/users', createCRUD(User));      // → /api/users, /api/users/:id
app.use('/v2/people', createCRUD(User));      // → /v2/people, /v2/people/:id

6. toTypeScript — Generate TypeScript Types

Purpose: Output a TypeScript interface string from your schema. Use for types in your frontend or to write a .d.ts file.

Basic usage

const { defineSchema, toTypeScript } = require('schema-flow');

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true },
    email: { type: 'email', required: true },
    age:   { type: 'number' },
  },
});

const ts = toTypeScript(User);
console.log(ts);

Output:

export interface User {
  name: string;
  email: string;
  age?: number | undefined;
}

Writing to a file

const fs = require('fs');
fs.writeFileSync('src/types/User.d.ts', toTypeScript(User));

In a build script

You can add an npm script to regenerate types when schemas change:

{
  "scripts": {
    "generate:types": "schema-flow generate"
  }
}

7. createAPIClient — API Client

Purpose: Type-safe API client for frontend to call your CRUD API. Matches routes from createCRUD.

Basic usage

import { defineSchema, createAPIClient } from 'schema-flow';

const User = defineSchema({
  name: 'User',
  fields: {
    name: { type: 'string', required: true },
    email: { type: 'email', required: true },
  },
});

const userAPI = createAPIClient(User, { baseUrl: '/api' });

// All methods return Promises
const users = await userAPI.getAll();
const user = await userAPI.getById('507f1f77bcf86cd799439011');
const created = await userAPI.create({ name: 'Jane', email: '[email protected]' });
const updated = await userAPI.update(id, { name: 'Jane Doe' });
const deleted = await userAPI.delete(id);

Options

| Option | Type | Description | |--------|------|-------------| | baseUrl | string | Base URL (e.g. '/api' or 'https://api.example.com') | | fetch | function | Custom fetch (for auth, etc.) | | headers | object | Extra headers for all requests |


8. useSchemaForm — React Form Hook

Purpose: Form state + validation driven by your schema. Import from schema-flow/react.

Basic usage

import { defineSchema } from 'schema-flow';
import { useSchemaForm } from 'schema-flow/react';

const User = defineSchema({
  name: 'User',
  fields: {
    name: { type: 'string', required: true, minLength: 2 },
    email: { type: 'email', required: true },
  },
});

function UserForm() {
  const { values, errors, handleChange, handleSubmit } = useSchemaForm(User);

  return (
    <form onSubmit={handleSubmit(async (data) => {
      await fetch('/api/users', { method: 'POST', body: JSON.stringify(data) });
    })}>
      <input
        value={values.name ?? ''}
        onChange={handleChange('name')}
        placeholder="Name"
      />
      {errors.name && <span className="error">{errors.name}</span>}
      <input
        value={values.email ?? ''}
        onChange={handleChange('email')}
        placeholder="Email"
      />
      {errors.email && <span className="error">{errors.email}</span>}
      <button type="submit">Save</button>
    </form>
  );
}

Return value

| Property | Description | |----------|-------------| | values | Current form values | | errors | Field errors { field: message } | | setValue | Set single field | | setValues | Set multiple fields | | handleChange | Returns onChange handler for input | | handleSubmit | Returns onSubmit handler (validates first) | | validate | Run validation manually | | reset | Reset form to initial values |


9. generateTypes & watchAndRegenerate

Purpose: Write TypeScript types to a file; watch for changes and regenerate.

generateTypes

const { defineSchema, generateTypes } = require('schema-flow');

const User = defineSchema({ name: 'User', fields: { ... } });
const Post = defineSchema({ name: 'Post', fields: { ... } });

generateTypes([User, Post], './src/types/generated.d.ts');

watchAndRegenerate

Requires a config file (e.g. schema-flow.config.js):

module.exports = {
  schemas: require('./schemas'),
  output: './src/types/generated.d.ts',
};
const { watchAndRegenerate } = require('schema-flow');
watchAndRegenerate({ configPath: './schema-flow.config.js' });

10. CLI

# Scaffold new project
npx schema-flow init ./my-app

# Generate types (uses schema-flow.config.js by default)
npx schema-flow generate

# Watch and regenerate on change
npx schema-flow watch

schema-flow.config.js

module.exports = {
  schemas: { User: require('./schemas/User'), Post: require('./schemas/Post') },
  output: './src/types/generated.d.ts',
};

All Field Types & Options

Field types

| Type | Description | Example | |------|-------------|---------| | string | Text | { type: 'string' } | | number | Number | { type: 'number' } | | boolean | true/false | { type: 'boolean' } | | date | Date | { type: 'date' } | | email | Email format | { type: 'email' } | | url | URL format | { type: 'url' } | | objectId | MongoDB ObjectId | { type: 'objectId', ref: 'User' } | | array | Array | { type: 'array', items: { type: 'string' } } | | object | Nested object | { type: 'object', fields: { ... } } |

Field options

| Option | Applies to | Type | Description | |--------|------------|------|-------------| | required | all | boolean | Field must be present | | default | all | any | Default when value is empty | | minLength | string | number | Minimum string length | | maxLength | string | number | Maximum string length | | pattern | string | RegExp | string | Regex for validation | | min | number | number | Minimum value | | max | number | number | Maximum value | | enum | string, number | string[] | number[] | Allowed values | | ref | objectId | string | Referenced model name | | items | array | SchemaFieldDefinition | Schema for array items | | fields | object | SchemaDefinition | Schema for nested object |

Examples

// String with constraints
{ type: 'string', required: true, minLength: 2, maxLength: 100 }

// Number with range
{ type: 'number', min: 0, max: 100, default: 0 }

// Enum
{ type: 'string', enum: ['admin', 'user', 'guest'] }

// ObjectId with reference
{ type: 'objectId', ref: 'User' }

// Array of strings
{ type: 'array', items: { type: 'string' } }

// Nested object
{
  type: 'object',
  fields: {
    street: { type: 'string' },
    city:   { type: 'string' },
    zip:    { type: 'string' },
  },
}

Complete Example

const express = require('express');
const mongoose = require('mongoose');
const {
  defineSchema,
  validate,
  createMongooseModel,
  validateRequestBody,
  createCRUD,
  toTypeScript,
  createAPIClient,
  generateTypes,
} = require('schema-flow');

const User = defineSchema({
  name: 'User',
  fields: {
    name:  { type: 'string', required: true, minLength: 2 },
    email: { type: 'email', required: true },
    age:   { type: 'number', min: 0, max: 150 },
  },
});

// 1. Validate without DB
const result = validate(User, { name: 'John', email: '[email protected]', age: 30 });

// 2. Generate TypeScript (to string or file)
const ts = toTypeScript(User);
generateTypes(User, './src/types/generated.d.ts');

// 3. Express app with CRUD
const app = express();
app.use(express.json());

await mongoose.connect('mongodb://localhost/test');
app.use('/api/users', createCRUD(User));

// 4. API client for frontend
const userAPI = createAPIClient(User, { baseUrl: '/api' });
// userAPI.getAll(), userAPI.create(data), etc.

// 5. Custom route with validation
app.post('/custom', validateRequestBody(User), (req, res) => {
  const data = req.validated;
  // ...
});

app.listen(3000);

Roadmap

  • [x] Phase 1: Schema + validation
  • [x] Phase 2: Express middleware + TypeScript generation
  • [x] Phase 3: Mongoose + CRUD
  • [x] Phase 4: React hooks (useSchemaForm)
  • [x] Phase 5: API client generator
  • [x] Phase 6: CLI + type watcher

License

MIT