@sens-tools/base-server
v0.0.4
Published
A reusable Express server base with Swagger documentation support
Readme
Sens Node.js Base Server
A powerful Express server package that provides a robust foundation for building secure and well-documented APIs with Swagger integration, authentication, rate limiting, and more.
Features
🛡️ Security Features
- Helmet for security headers
- CORS support with configurable options
- Rate limiting to prevent abuse
- Basic authentication for Swagger UI
- Toobusy protection against server overload
📚 Swagger Integration
- Multiple theme support (classic, dark, material, monokai, muted, newspaper, outline, shadow)
- Instance-specific documentation
- Basic authentication protection
- Customizable base URL and API path
🚀 Express Enhancements
- Instance-based routing with
createRouterfunction - Standardized response helper
- Error handling middleware
- Morgan logging with configurable formats
- Body parsing middleware
- Instance-based routing with
Installation
pnpm add @sens-tools/base-serverQuick Start
import { SensServer } from '@sens-tools/base-server';
import { Router } from 'express';
// Create your router function
const createRouter = (router: Router): Router => {
// Add your routes here
router.get('/users', (req, res) => {
console.log('Instance:', req.api.iota.instance);
console.log('Token:', req.api.iota.token);
res.api.ok({ users: [] });
});
router.post('/users', (req, res) => {
res.api.created({ id: 1, name: 'John Doe' });
});
return router;
};
// Create your Swagger documentation
const swaggerJson = {
info: {
title: 'Your API Server',
version: '1.0.0',
},
openapi: '3.0.0',
paths: {
'/users': {
get: {
responses: {
'200': {
description: 'List of users',
},
},
},
post: {
responses: {
'201': {
description: 'User created',
},
},
},
},
},
};
// Initialize the server with configuration
const server = new SensServer({
createRouter,
swaggerJson,
iotaBaseUrl: 'your-domain.com',
apiPath: '/api',
swaggerPath: '/docs',
swaggerUsername: 'admin',
swaggerPassword: 'secret',
morgan: 'dev',
rateLimitWindowMs: 15 * 60 * 1000, // 15 minutes
rateLimitMaxRequests: 100,
toobusyMaxLag: 100,
toobusyInterval: 500,
corsOptions: {
origin: ['https://your-frontend.com'],
methods: ['GET', 'POST'],
},
});
// Start the server
server.start(3000);Available Components
import SensServer, { handleEndpointResult } from '@sens-tools/base-server';Exported Types
The package exports the following TypeScript types:
import type {
SensConfig, // Complete server configuration with all required fields
SensConfigInput, // Input configuration interface for SensServer constructor
InstanceConfig, // Configuration for API instances
EndpointResultType, // Type for endpoint result data (string | number | Record<string, unknown> | Array<unknown>)
EndpointFunction, // Type for endpoint functions that return a Result type
ErrorResponse, // Standard error response format
} from '@sens-tools/base-server';Configuration Options
The SensConfigInput interface accepts the following configuration:
interface SensConfigInput {
createRouter: (router: Router) => Router; // Function to create and configure your router
swaggerJson: {
info: {
title: string;
version: string;
};
openapi: string;
paths: Record<string, unknown>;
}; // Your OpenAPI/Swagger documentation
iotaBaseUrl: string; // Base URL for your API (e.g., 'api.example.com')
corsOptions?: CorsOptions; // Optional CORS configuration
swaggerUsername?: string; // Username for Swagger UI basic auth (default: 'swagger')
swaggerPassword?: string; // Password for Swagger UI basic auth (default: 'secret')
morgan?: 'combined' | 'common' | 'dev' | 'short' | 'tiny'; // Logging format (default: 'dev')
toobusyMaxLag?: number; // Maximum event loop lag in milliseconds (default: 100)
toobusyInterval?: number; // Check interval in milliseconds (default: 500)
rateLimitWindowMs?: number; // Rate limit window in milliseconds (default: 300000 - 5 minutes)
rateLimitMaxRequests?: number; // Maximum requests per window (default: 1000)
apiPath?: string; // API path prefix (default: '/api')
swaggerPath?: string; // Swagger UI path (default: '/swagger')
}SensServer Components and Functionality
After initializing a new SensServer instance, you have access to several powerful components and methods:
Available Methods
// Get the router to add your routes (deprecated - use createRouter instead)
const router = server.getRouter();
// Get the Express app instance for advanced customization
const app = server.getApp();
// Get the current server configuration
const config = server.getConfig();
// Start the server
await server.start(port);
// Shutdown the server gracefully
await server.shutdown();
// Restart the server
await server.restart(port);Request Context
Each request automatically includes instance and authentication information through req.api:
req.api.iota.instance; // The instance identifier from the URL
req.api.iota.token; // The authentication token from headersResponse Helpers
The server provides a rich set of response helpers through res.api:
res.api.ok(data); // 200 OK
res.api.created(data); // 201 Created
res.api.accepted(data); // 202 Accepted
res.api.noContent(); // 204 No Content
res.api.badRequest(error); // 400 Bad Request
res.api.unauthorized(error); // 401 Unauthorized
res.api.forbidden(error); // 403 Forbidden
res.api.notFound(error); // 404 Not Found
res.api.methodNotAllowed(error); // 405 Method Not Allowed
res.api.notAcceptable(error); // 406 Not Acceptable
res.api.requestTimeout(error); // 408 Request Timeout
res.api.conflict(error); // 409 Conflict
res.api.gone(error); // 410 Gone
res.api.unprocessableEntity(error); // 422 Unprocessable Entity
res.api.internalServerError(error); // 500 Internal Server Error
res.api.serviceUnavailable(error); // 503 Service Unavailable
res.api.tooBusy(error); // 503 Too Busy
res.api.gatewayTimeout(error); // 504 Gateway TimeoutError Handling with handleEndpointResult
The SensServer provides a powerful error handling system through the handleEndpointResult utility. This utility helps standardize error handling across your API endpoints and ensures consistent error responses.
Basic Usage
import { handleEndpointResult } from '@sens-tools/base-server';
import { ok, err } from 'neverthrow';
// Define your endpoint function
const getUsers = async (req: Request, res: Response) => {
const users = await getUsersFromDatabase();
if (users.isErr()) {
return err(users.error);
}
return ok(users.value);
};
// Use handleEndpointResult in your route
router.get('/users', (req, res) => {
handleEndpointResult(req, res, getUsers);
});Error Response Format
All errors follow a consistent format:
{
error: string, // Error code/type
error_description: string, // Human-readable error message
details?: any // Optional additional error details (e.g., validation errors)
}Error Types Handled
The system automatically handles various types of errors:
Validation Errors (Zod)
{ error: 'Validation Error', error_description: 'The request data failed validation', details: [ { path: 'user.email', message: 'Invalid email format', code: 'invalid_string' } ] }API Errors
{ error: 'API Error', error_description: 'Custom error message', statusCode: 400 // Optional status code }Standard Errors
{ error: 'Internal Server Error', error_description: 'An unexpected error occurred' }
Example with Error Handling
import { handleEndpointResult } from '@sens-tools/base-server';
import { ok, err } from 'neverthrow';
const createUser = async (req: Request, res: Response) => {
try {
// Validate input
const validation = userSchema.safeParse(req.body);
if (!validation.success) {
return err(validation.error);
}
// Create user
const user = await db.users.create(validation.data);
if (!user) {
return err({
error: 'User Creation Failed',
error_description: 'Failed to create user in database',
});
}
// Return success
return ok(user);
} catch (error) {
return err(error);
}
};
router.post('/users', (req, res) => {
handleEndpointResult(req, res, createUser);
});Advanced Usage Examples
Complete Server Setup
import { SensServer } from '@sens-tools/base-server';
import { Router } from 'express';
import { z } from 'zod';
// Define your schemas
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
// Create your router function
const createRouter = (router: Router): Router => {
// User routes
router.get('/users', (req, res) => {
res.api.ok({ users: [] });
});
router.post('/users', (req, res) => {
const validation = userSchema.safeParse(req.body);
if (!validation.success) {
return res.api.badRequest({
error: 'Validation Error',
error_description: 'Invalid user data',
details: validation.error.errors,
});
}
res.api.created({ id: 1, ...validation.data });
});
// Product routes
router.get('/products', (req, res) => {
res.api.ok({ products: [] });
});
return router;
};
// Swagger documentation
const swaggerJson = {
info: {
title: 'My API Server',
version: '1.0.0',
description: 'A comprehensive API server',
},
openapi: '3.0.0',
servers: [
{
url: 'https://api.example.com',
description: 'Production server',
},
],
paths: {
'/users': {
get: {
summary: 'Get all users',
responses: {
'200': {
description: 'List of users',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
users: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
},
},
},
},
},
},
},
post: {
summary: 'Create a new user',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' },
},
required: ['name', 'email'],
},
},
},
},
responses: {
'201': {
description: 'User created successfully',
},
'400': {
description: 'Invalid input data',
},
},
},
},
'/products': {
get: {
summary: 'Get all products',
responses: {
'200': {
description: 'List of products',
},
},
},
},
},
};
// Initialize server
const server = new SensServer({
createRouter,
swaggerJson,
iotaBaseUrl: 'api.example.com',
swaggerUsername: 'admin',
swaggerPassword: 'secure-password',
corsOptions: {
origin: ['https://my-frontend.com'],
credentials: true,
},
rateLimitMaxRequests: 500,
morgan: 'combined',
});
// Start server
server.start(3000).then((port) => {
console.log(`🚀 Server running on port ${port}`);
console.log(`📚 Swagger docs available at http://localhost:${port}/swagger`);
});Custom Middleware Integration
import { SensServer } from '@sens-tools/base-server';
import { Router } from 'express';
const createRouter = (router: Router): Router => {
// Add custom middleware to specific routes
router.use('/admin', (req, res, next) => {
if (req.api.iota.token !== 'admin-token') {
return res.api.forbidden({ error: 'Admin access required' });
}
next();
});
router.get('/admin/users', (req, res) => {
res.api.ok({ adminUsers: [] });
});
return router;
};
const server = new SensServer({
createRouter,
swaggerJson: {
/* your swagger config */
},
iotaBaseUrl: 'api.example.com',
});
// Add global middleware
const app = server.getApp();
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});Swagger Documentation
Access your API documentation through multiple endpoints:
- Default theme:
/${swaggerPath} - Theme-specific:
/${swaggerPath}/{theme} - Instance-specific JSON:
/${swaggerPath}/{theme}/{instance}/swagger.json
Available themes:
- classic (default)
- dark
- material
- monokai
- muted
- newspaper
- outline
- shadow
Development
Clone the repository
Install dependencies:
pnpm installBuild the project:
pnpm build
Version Management
This project uses Bumpp for version management. See BUMPP.md for detailed instructions.
Contributing
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a new Pull Request
