schema-driven-flow
v1.0.0
Published
Define schema once → Mongoose, TypeScript, validation, React forms, API client. Full-stack schema engine for MERN.
Maintainers
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
- What's Not Yet Built
- Install
- 1. defineSchema — Define Your Schema
- 2. validate — Validate Data Anywhere
- 3. Mongoose — toMongooseSchema & createMongooseModel
- 4. Express — Validation Middleware
- 5. createCRUD — Auto REST API
- 6. toTypeScript — Generate TypeScript Types
- 7. createAPIClient — API Client
- 8. useSchemaForm — React Form Hook
- 9. generateTypes & watchAndRegenerate
- 10. CLI
- All Field Types & Options
- Complete Example
- Roadmap
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 react1. 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
Schemainstance withname,fields, andtimestamps - 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/:id6. 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 watchschema-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
