JSPM

schema-driven-flow

1.0.0
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 6
    • Score
      100M100P100Q56348F
    • License MIT

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

    Package Exports

    • schema-driven-flow
    • schema-driven-flow/react

    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: 'john@x.com' });
    console.log(result1.valid);   // true
    console.log(result1.data);    // { name: 'John', email: 'john@x.com' }
    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: 'jane@x.com' });
    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: 'jane@x.com' });
    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: 'j@x.com', 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

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

    License

    MIT