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
- 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: '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/: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: '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 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: '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