A powerful OpenAPI v3 compliant REST framework for Node.js with comprehensive MongoDB support, JSON Schema validation, authentication, and authorization. Build production-ready RESTful APIs in minutes with automatic OpenAPI documentation generation.
- âś… OpenAPI v3 Compliant: Automatic OpenAPI specification generation with full v3 support
- âś… MongoDB Integration: Built-in MongoDB operations (CRUD) with advanced querying
- âś… JSON Schema Validation: Comprehensive input validation using JSON Schema
- âś… Authentication & Authorization: OAuth2 scopes and CASL ability-based permissions
- âś… Resource Query Language (RQL): Advanced querying with filtering, sorting, and pagination
- âś… Express.js Integration: Works seamlessly with existing Express.js applications
- âś… CSV Export: Built-in CSV export functionality for data endpoints
- âś… JSON-RPC Support: Dual REST and JSON-RPC endpoint support
- âś… Pipeline Operations: Complex data processing with pipeline support
- âś… TypeScript Support: Full TypeScript definitions included
- âś… Modern ES Modules: Supports both ESM and CommonJS
# npm
npm install arrest
# pnpm
pnpm add arrest
# yarn
yarn add arrest
import { API, MongoResource } from 'arrest';
const api = new API({
info: {
title: 'My API',
version: '1.0.0'
}
});
// Add a MongoDB-backed resource
api.addResource(new MongoResource('mongodb://localhost:27017/mydb', {
name: 'User',
collection: 'users'
}));
// Start the server
api.listen(3000);
console.log('API running at http://localhost:3000');
console.log('OpenAPI spec at http://localhost:3000/openapi.json');
This creates a full CRUD API for users with the following endpoints:
GET /users
- List users with filtering, sorting, paginationPOST /users
- Create a new userGET /users/{id}
- Get user by IDPUT /users/{id}
- Update userPATCH /users/{id}
- Partial update user with JSON PatchDELETE /users/{id}
- Delete user
Once your API is running, you can interact with it using curl or any HTTP client:
# List all users
curl "http://localhost:3000/users"
# Create a new user
curl "http://localhost:3000/users" \
-H "Content-Type: application/json" \
-X POST \
-d '{"name": "John Doe", "email": "[email protected]"}'
# Get a specific user
curl "http://localhost:3000/users/507f1f77bcf86cd799439011"
# Update a user
curl "http://localhost:3000/users/507f1f77bcf86cd799439011" \
-H "Content-Type: application/json" \
-X PUT \
-d '{"name": "John Smith", "email": "[email protected]"}'
# Delete a user
curl "http://localhost:3000/users/507f1f77bcf86cd799439011" -X DELETE
import { API, Resource, Operation } from 'arrest';
class CustomOperation extends Operation {
constructor(resource, path, method) {
super(resource, path, method, 'customOp');
}
getDefaultInfo() {
return {
summary: 'Custom operation',
description: 'Performs a custom operation',
responses: {
'200': {
description: 'Success',
content: {
'application/json': {
schema: { type: 'object' }
}
}
}
}
};
}
async handler(req, res, next) {
try {
const result = await this.runOperation({ req, res });
res.json(result);
} catch (error) {
next(error);
}
}
async runOperation(job) {
return { message: 'Custom operation executed', timestamp: new Date() };
}
}
const api = new API();
const resource = new Resource({ name: 'Custom' });
resource.addOperation(new CustomOperation(resource, '/action', 'post'));
api.addResource(resource);
arrest follows a three-tier architecture:
- API - The top-level container that manages resources and generates OpenAPI specifications
- Resource - A collection of related operations (e.g., User resource with CRUD operations)
- Operation - Individual HTTP endpoints that handle specific requests
import { API, Resource, Operation } from 'arrest';
// 1. Create API instance
const api = new API({
info: {
title: 'My REST API',
version: '1.0.0',
description: 'A comprehensive REST API built with arrest'
}
});
// 2. Create resource with operations
const userResource = new Resource({
name: 'User',
path: 'users' // Optional: defaults to plural of name
});
// 3. Add custom operations to resource
userResource.addOperation('/profile', 'get', async (req, res) => {
res.json({ profile: 'user profile data' });
});
// 4. Add resource to API
api.addResource(userResource);
// 5. Start the server
api.listen(3000);
arrest automatically converts resource names to RESTful paths:
// Resource name -> Path conversion
new Resource({ name: 'User' }); // -> /users
new Resource({ name: 'BlogPost' }); // -> /blog-posts
new Resource({ name: 'UserProfile' }); // -> /user-profiles
// Custom path override
new Resource({
name: 'User',
path: 'customers', // Custom path
namePlural: 'CustomerList' // Custom plural name
});
The main API container that manages resources and server configuration.
Constructor Options:
new API({
info: {
title: 'API Title',
version: '1.0.0',
description: 'API Description'
},
servers: [
{ url: 'https://api.example.com', description: 'Production' },
{ url: 'http://localhost:3000', description: 'Development' }
],
security: [
{ bearerAuth: [] }
]
})
Key Methods:
addResource(resource)
- Add a resource to the APIlisten(port, callback?)
- Start HTTP serverlisten({ http: 3000, https: 3443, options })
- Start HTTP/HTTPS serversrouter()
- Get Express router for integrationattach(app, path?)
- Attach to existing Express app
Represents a collection of related operations.
Constructor Options:
new Resource({
name: 'User', // Resource name (required)
path: 'users', // Custom path (optional)
namePlural: 'Users', // Custom plural name (optional)
description: 'User management' // OpenAPI description (optional)
})
Key Methods:
addOperation(path, method, handler)
- Add simple operationaddOperation(operationInstance)
- Add operation instance
Specialized resource for MongoDB collections with built-in CRUD operations.
Constructor:
new MongoResource(connectionUri, options, customRoutes?)
Options:
{
name: 'User', // Resource name
collection: 'users', // MongoDB collection name
id: '_id', // ID field name (default: '_id')
idIsObjectId: true, // Whether ID is ObjectId (default: true)
queryLimit: 100, // Maximum query results (default: no limit)
createIndexes: false, // Auto-create indexes (default: false)
escapeProperties: false // Escape MongoDB special characters (default: false)
}
Base class for individual API operations.
Constructor:
new Operation(resource, path, method, operationId)
Key Methods to Override:
getDefaultInfo()
- Return OpenAPI operation infohandler(req, res, next)
- Express request handlerrunOperation(job)
- Main operation logic
arrest provides comprehensive input validation using OpenAPI v3 and JSON Schema:
import { Operation } from 'arrest';
class CreateUserOperation extends Operation {
constructor(resource, path, method) {
super(resource, path, method, 'createUser');
}
getDefaultInfo() {
return {
summary: 'Create a new user',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['name', 'email'],
additionalProperties: false,
properties: {
name: {
type: 'string',
minLength: 1,
maxLength: 100
},
email: {
type: 'string',
format: 'email'
},
age: {
type: 'integer',
minimum: 0,
maximum: 150
}
}
}
}
}
},
parameters: [
{
name: 'include',
in: 'query',
schema: {
type: 'array',
items: { type: 'string', enum: ['profile', 'preferences'] }
},
style: 'form',
explode: false
}
]
};
}
async runOperation(job) {
const { name, email, age } = job.req.body;
const include = job.req.query.include || [];
// Create user logic here
return { id: '123', name, email, age, created: new Date() };
}
}
arrest supports powerful querying with RQL syntax:
// Examples of RQL queries
const queries = [
// Basic filtering
'eq(status,active)',
'gt(age,18)',
'in(category,electronics,books)',
// Complex queries
'and(eq(status,active),gt(price,100))',
'or(eq(category,sale),lt(price,50))',
// Sorting and pagination
'sort(+name,-created)',
'limit(10,20)', // limit(count, offset)
// Field selection
'select(name,email,created)'
];
// Use in API calls
// GET /users?q=and(eq(status,active),gt(age,18))&sort=-created&limit(10)
import { API, MongoResource } from 'arrest';
class SecureAPI extends API {
initSecurity(req, res, next) {
// Extract and validate OAuth2 token
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Missing authorization token' });
}
// Validate token and set scopes
req.scopes = ['read:users', 'write:users']; // From token validation
next();
}
}
class SecureOperation extends Operation {
get swaggerScopes() {
return {
'oauth2': ['read:users', 'write:users']
};
}
}
import { defineAbility } from '@casl/ability';
class AuthorizedAPI extends API {
initSecurity(req, res, next) {
// Define user abilities based on their role
req.ability = defineAbility((can, cannot) => {
if (req.user.role === 'admin') {
can('manage', 'all');
} else if (req.user.role === 'user') {
can('read', 'User', { ownerId: req.user.id });
can('update', 'User', { ownerId: req.user.id });
cannot('delete', 'User');
}
});
next();
}
}
import { MongoResource, QueryMongoOperation } from 'arrest';
class AdvancedUserResource extends MongoResource {
constructor() {
super('mongodb://localhost:27017/myapp', {
name: 'User',
collection: 'users',
createIndexes: true
});
}
getIndexes() {
return [
{ key: { email: 1 }, unique: true },
{ key: { 'profile.tags': 1 } },
{ key: { createdAt: -1 } }
];
}
}
// Custom aggregation operation
class UserStatsOperation extends QueryMongoOperation {
async prepareQuery(job) {
// Return MongoDB aggregation pipeline instead of simple query
return [
{ $match: { status: 'active' } },
{ $group: {
_id: '$department',
count: { $sum: 1 },
averageAge: { $avg: '$age' }
}
},
{ $sort: { count: -1 } }
];
}
}
Built-in CSV export functionality:
// GET /users?format=csv&csv_fields=name,email,created
// GET /users?format=csv&csv_fields=name,email&csv_options=header=true&csv_names=Name,Email
arrest supports dual REST and JSON-RPC interfaces:
import { RPCOperation } from 'arrest';
class UserRPCOperation extends RPCOperation {
async getUserProfile(params) {
const { userId } = params;
// Fetch user profile logic
return { profile: { id: userId, name: 'John Doe' } };
}
async updateUserProfile(params) {
const { userId, updates } = params;
// Update logic
return { success: true, updated: updates };
}
}
// JSON-RPC calls:
// POST /users/rpc
// {"jsonrpc": "2.0", "method": "getUserProfile", "params": {"userId": "123"}, "id": 1}
Complex data processing with pipeline support:
import { PipelineOperation } from 'arrest';
class DataProcessingPipeline extends PipelineOperation {
async runOperation(job) {
let data = await super.runOperation(job);
// Apply transformations
data = this.filterSensitiveData(data);
data = this.calculateDerivedFields(data);
data = this.formatForOutput(data, job.req.query.format);
return data;
}
filterSensitiveData(data) {
// Remove sensitive fields based on user permissions
return data.map(item => this.filterFields(item, job.req.ability));
}
}
arrest works seamlessly with existing Express applications:
import express from 'express';
import { API, MongoResource } from 'arrest';
const app = express();
const api = new API();
// Add resources to API
api.addResource(new MongoResource('mongodb://localhost:27017/mydb', {
name: 'User'
}));
// Mount API on Express app
app.use('/api/v1', await api.router());
// Add other Express routes
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
app.listen(3000);
Comprehensive error handling with detailed responses:
import { API } from 'arrest';
class CustomAPI extends API {
handleError(error, req, res, next) {
if (error.name === 'ValidationError') {
return res.status(400).json({
error: 'Validation failed',
details: error.errors,
path: error.path
});
}
if (error.code === 11000) { // MongoDB duplicate key
return res.status(409).json({
error: 'Resource already exists',
field: Object.keys(error.keyPattern)[0]
});
}
// Default error handling
super.handleError(error, req, res, next);
}
}
Full TypeScript definitions are included:
import { API, MongoResource, Operation } from 'arrest';
import { Request, Response } from 'express';
interface User {
id: string;
name: string;
email: string;
createdAt: Date;
}
class TypedUserOperation extends Operation {
async runOperation(job: { req: Request; res: Response }): Promise<User> {
const userData = job.req.body as Partial<User>;
// Implementation with type safety
return {
id: 'generated-id',
name: userData.name!,
email: userData.email!,
createdAt: new Date()
};
}
}
- Use MongoDB indexes - Define indexes for frequently queried fields
- Implement caching - Use Redis or memory caching for frequently accessed data
- Limit query results - Set reasonable queryLimit on resources
- Use projections - Only fetch needed fields with the
fields
parameter - Enable compression - Use gzip compression in production
import { API, MongoResource } from 'arrest';
const api = new API({
info: { title: 'Production API', version: '1.0.0' }
});
// Production MongoDB resource with optimization
api.addResource(new MongoResource('mongodb://mongo-cluster/prod-db', {
name: 'User',
collection: 'users',
queryLimit: 100, // Limit results
createIndexes: true, // Auto-create indexes
escapeProperties: true // Security: escape special chars
}));
// Start with both HTTP and HTTPS
api.listen({
http: 8080,
https: 8443,
httpsOptions: {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem')
}
});
MIT License - see the LICENSE file for details.
Contributions are welcome! Please ensure all tests pass:
pnpm install
pnpm test
pnpm run coverage
- jsonref - JSON Reference resolution
- jsonpolice - JSON Schema validation
- openapi-police - OpenAPI validation utilities