Weave is a modern, chainable API development framework built on Express.js. It provides a clean, functional approach to building REST APIs with built-in middleware chaining, database integration, and validation.
- Features
- Installation
- Quick Start
- Core Concepts
- How Weave Works Internally
- API Reference
- Sub-Modules
- Examples
- 🔗 Chainable API: Clean, readable middleware chains
- 🎯 Type-safe: Built with TypeScript
- 🗃️ Database Integration: Mongoose helpers for common operations
- ✅ Built-in Validation: JSON Schema validation support
- 🔒 SSL Support: HTTPS out of the box
- 📝 Automatic Logging: Request/response logging with timestamps
- ⚡ Async/Await: Full promise support throughout
npm install @weave-protocol/express# Mongoose database helpers
npm install @weave-protocol/mongoose
# JSON Schema validation
npm install @weave-protocol/json-schema-validator
# JWT authentication
npm install @weave-protocol/jsonwebtoken
# Socket.IO integration
npm install @weave-protocol/socketimport Weave from "@weave-protocol/express";
const api = new Weave({
port: 8000
});
api.endpoint('/hello', 'GET')
.send({ message: 'Hello, World!' });Weave uses a middleware chain pattern where each method adds a function to a queue. These functions are executed sequentially when a request is received. Each function can:
- Return
trueto continue to the next middleware - Return an object with
codeto send a response - Return a
Promisefor async operations
- Map Phase: Extract data from request (body, params, headers, query)
- Use Phase: Process and validate the mapped data
- DB Phase: Perform database operations
- Send Phase: Send the response
When you create a Weave instance, it:
- Initializes an Express app
- Sets up custom response methods (
sendOk,sendError) - Configures JSON body parsing
- Creates an HTTP/HTTPS server
- Initializes an internal registry to store endpoint handlers
When you call endpoint(path, method):
- A unique key is created (
method:path) - An entry is created in the registry with an empty middleware array
- Express route handler is registered
- The handler will execute the middleware chain when a request arrives
When a request comes in:
- Request data is stored in the registry instance
- Middleware functions are executed sequentially
- Each function's return value determines flow:
true: Continue to next middlewarefalseor error object: Stop and send error- Promise: Wait for resolution, then check value
Request → mapBody → useBody → mapDB → useDB → send → Response
↓ ↓ ↓ ↓ ↓
Extract Validate Setup Query Response
Data Data DB DB Data
new Weave(options: WeaveConfiguration)Options:
port?: number- Server port (default: 8000)driver?: "express"- HTTP driveruseJSON?: boolean- Enable JSON parsing (default: true)sslKey?: string- Path to SSL key filesslCert?: string- Path to SSL certificate filedbConnection?: any- Mongoose connection instance
endpoint(path: string, method: "POST" | "GET" | "DELETE"): WeaveCreates a new endpoint. All subsequent chained methods apply to this endpoint.
Example:
api.endpoint('/users', 'GET')Extracts specified keys from request body.
api.endpoint('/login', 'POST')
.mapBody(['email', 'password'])Extracts specified keys from URL parameters.
api.endpoint('/users/:id', 'GET')
.mapParams(['id'])Extracts specified keys from query string.
api.endpoint('/search', 'GET')
.mapQuery(['q', 'limit'])Extracts specified keys from request headers.
api.endpoint('/protected', 'GET')
.mapHeader(['authorization'])Process or validate body data.
.useBody((body, instance) => {
if (!body.email.includes('@')) {
return { code: 400, message: 'Invalid email' };
}
return true;
})Process URL parameters.
.useParams((params, instance) => {
if (!params.id.match(/^[0-9]+$/)) {
return { code: 400, message: 'Invalid ID' };
}
return true;
})Process query parameters.
.useQuery((query, instance) => {
query.limit = parseInt(query.limit) || 10;
return true;
})Process headers (e.g., authentication).
.useHeader((header, instance) => {
if (!header.authorization) {
return { code: 401, message: 'Unauthorized' };
}
return true;
})Maps database model and schema for subsequent operations.
.mapDB('users', UserSchema)Executes custom database operations.
.useDB(async (db, instance) => {
const [modelName, schema] = db;
// Perform database operations
return true;
})Access the shared store object (contains request metadata like IP address).
.useStore((store, instance) => {
console.log('Request from:', store.ip);
return true;
})Sends the response. Can be a static object or a function that returns data.
.send({ message: 'Success!' })
// Or with a function
.send((instance) => {
return {
message: 'Success',
user: instance.body.email
};
})Database helpers for MongoDB operations via Mongoose.
import { CheckIfExists, FetchWhere, Insert, UpdateWhere } from "@weave-protocol/mongoose";CheckIfExists - Check if a document exists
api.endpoint('/register', 'POST')
.mapBody(['email'])
.mapDB('users', UserSchema)
.useDB(CheckIfExists.fromBody(['email'],
() => ({ code: 400, message: 'User already exists' }),
() => true
))FetchWhere - Fetch documents with conditions
.useDB(FetchWhere.fromBody(['email'],
(data, instance) => {
instance.store.user = data;
return true;
},
() => ({ code: 404, message: 'User not found' })
))FetchOne - Fetch a single document
.useDB(FetchOne.fromParams(['id'],
(user, instance) => {
return { code: 200, data: user };
},
() => ({ code: 404, message: 'Not found' })
))Insert - Insert a new document
.useDB(Insert.fromBody(['email', 'name'],
(result, instance) => true,
() => ({ code: 500, message: 'Insert failed' })
))UpdateWhere - Update documents matching criteria
.useDB(UpdateWhere(['email']).fromBody(['email', 'name'],
(result) => ({ code: 200, message: 'Updated' }),
() => ({ code: 500, message: 'Update failed' })
))DeleteOne - Delete a document
.useDB(DeleteOne.fromParams(['id'],
() => ({ code: 200, message: 'Deleted' }),
() => ({ code: 404, message: 'Not found' })
))Fetch - Fetch all documents with pagination
.useDB(Fetch.withLimit({ start: 0, limit: 10 },
(data) => ({ code: 200, data }),
() => ({ code: 500, message: 'Fetch failed' })
))Count - Count documents
.useDB(Count.fromQuery(['status'],
(count) => ({ code: 200, count }),
() => ({ code: 500, message: 'Count failed' })
))import Mongoose from "mongoose";
import Weave from "@weave-protocol/express";
import { CheckIfExists, Insert } from "@weave-protocol/mongoose";
const db = Mongoose.createConnection('mongodb://localhost:27017/myapp');
const UserSchema = new Mongoose.Schema({
email: String,
name: String,
password: String
});
const api = new Weave({
port: 8000,
dbConnection: db
});
api.endpoint('/register', 'POST')
.mapBody(['email', 'name', 'password'])
.mapDB('users', UserSchema)
.useDB(CheckIfExists.fromBody(['email'],
() => ({ code: 400, message: 'Email already exists' }),
() => true
))
.useDB(Insert.fromBody(['email', 'name', 'password'],
() => ({ code: 201, message: 'User created' }),
() => ({ code: 500, message: 'Registration failed' })
));JSON Schema validation for request data.
import { JSONSchemaValidator, ShortText, LongText, Username, Password } from "@weave-protocol/json-schema-validator";- ShortText: 1-64 characters
- LongText: 4-255 characters
- Username: 2-32 characters
- Password: 4-255 characters
All validators support .required() method.
api.endpoint('/login', 'POST')
.mapBody(['email', 'password'])
.useBody(JSONSchemaValidator({
email: ShortText.required(),
password: LongText.required()
}))
.send({ message: 'Valid input' });.useBody(JSONSchemaValidator({
age: { type: 'number', minimum: 18, maximum: 120 },
name: { type: 'string', minLength: 2, maxLength: 50 }
}))JWT token validation and generation (authentication middleware).
import { ValidateToken, GenerateToken } from "@weave-protocol/jsonwebtoken";Socket.IO integration for real-time features.
import WeaveSocket from "@weave-protocol/socket";import Mongoose from "mongoose";
import Weave from "@weave-protocol/express";
import { JSONSchemaValidator, ShortText } from "@weave-protocol/json-schema-validator";
import { CheckIfExists, FetchWhere, Insert } from "@weave-protocol/mongoose";
// Database setup
const db = Mongoose.createConnection('mongodb://localhost:27017/blog');
const PostSchema = new Mongoose.Schema({
title: String,
content: String,
author: String,
createdAt: { type: Date, default: Date.now }
});
// API setup
const api = new Weave({
port: 3000,
dbConnection: db
});
// Create post
api.endpoint('/posts', 'POST')
.mapBody(['title', 'content', 'author'])
.useBody(JSONSchemaValidator({
title: ShortText.required(),
content: { type: 'string', minLength: 10 },
author: ShortText.required()
}))
.mapDB('posts', PostSchema)
.useDB(Insert.fromBody(['title', 'content', 'author'],
(post) => ({ code: 201, data: post }),
() => ({ code: 500, message: 'Failed to create post' })
));
// Get all posts
api.endpoint('/posts', 'GET')
.mapDB('posts', PostSchema)
.useDB(async (db, instance) => {
const [modelName, schema] = db;
const model = instance.options.dbConnection.model(modelName, schema);
const posts = await model.find().limit(20);
instance.response.sendOk({ code: 200, data: posts });
return true;
});
// Get single post
api.endpoint('/posts/:id', 'GET')
.mapParams(['id'])
.mapDB('posts', PostSchema)
.useDB(async (db, instance) => {
const [modelName, schema] = db;
const model = instance.options.dbConnection.model(modelName, schema);
const post = await model.findById(instance.params.id);
if (post) {
instance.response.sendOk({ code: 200, data: post });
} else {
instance.response.sendError({ code: 404, message: 'Post not found' });
}
return true;
});api.endpoint('/profile', 'GET')
.mapHeader(['authorization'])
.useHeader((header, instance) => {
const token = header.authorization?.replace('Bearer ', '');
if (!token) {
return { code: 401, message: 'No token provided' };
}
// Verify token (implement your JWT logic)
try {
instance.store.user = verifyToken(token);
return true;
} catch (e) {
return { code: 401, message: 'Invalid token' };
}
})
.send((instance) => ({
message: 'Profile data',
user: instance.store.user
}));const api = new Weave({
port: 443,
sslKey: './ssl/private.key',
sslCert: './ssl/certificate.crt'
});- Always validate input: Use
JSONSchemaValidatoror custom validators - Handle errors gracefully: Return proper error codes and messages
- Use async/await: For database operations and external API calls
- Keep middleware focused: Each middleware function should do one thing
- Store shared data: Use
instance.storeto pass data between middleware - Type your schemas: Use TypeScript interfaces for better IDE support
Successful responses (via sendOk):
{
"status": 200,
"message": "Success",
"data": { }
}Error responses (via sendError):
{
"status": 400,
"code": 400,
"message": "Error message",
"stack": { }
}MIT
Contributions are welcome! Please open an issue or submit a pull request.