mikroserve
v1.1.1
Published
Minimalistic, ready-to-use API, built on Node.js primitives.
Maintainers
Readme
MikroServe
Minimalistic, ready-to-use API, built on Node.js primitives.
- Native Node.js http/https/http2 implementation, meaning maximum performance
- Hono-style API semantics for GET, POST, PATCH, PUT, DELETE operations
- Binary file upload/download support (images, PDFs, videos, etc.)
- Multipart form-data parsing for HTML file uploads
- Supports being exposed over HTTP, HTTPS, and HTTP2
- Supports custom middlewares
- Out-of-the-box CORS support
- Built-in customizable rate limiter
- Configurable request timeout protection against slow clients
- Configurable body size limits
- Tiny (~5kb gzipped)
- Only a single dependency: MikroConf
Installation
npm install mikroserve -SUsage
Quick Start
A minimum example of a tiny MikroServe API could look like the below.
import { MikroServe, type Context } from 'mikroserve';
// Create an instance of MikroServe using only default values
const api = new MikroServe();
// Add any routes that should be exposed
// This will expose a GET route on the root of the API, responding with plain text
api.get('/', async (c: Context) => c.text('Hello world!'));
// JSON responses are as easy as...
api.get('/users/:userId', async (c: Context) => c.json({ name: 'Sam Person', id: 'abc123', createdAt: 1743323168 }));
// Example POST request with JSON response and custom status code
api.post('/users/:userId', async (c: Context) => {
const { name } = c.body; // Body is ready to use, no need for parsing
const userId = c.params.userId;
// Do your things...
return c.json({ success: true }, 201);
});
// MikroServe supports raw, binary, text, form, json, html, redirect response types
// Start the server
api.start();
// The API is ready – go ahead and curl it in your command line of choice
// HTTP: curl 0.0.0.0:3000
// HTTPS or HTTP2: curl -k 0.0.0.0:3000
// The response should be "Hello world!"Bigger example
import { MikroServe } from 'mikroserve';
// Create a new API instance
const api = new MikroServe({
// These are the default values
port: 3000,
host: '0.0.0.0',
useHttps: false,
useHttp2: false,
sslCert: '',
sslKey: '',
sslCa: '',
debug: false,
rateLimit: {
requestsPerMinute: 100,
enabled: true
},
allowedDomains: ['*']
});
// Define a global middleware for logging
api.use(async (c, next) => {
console.log(`Request received: ${c.req.method} ${c.path}`);
const start = Date.now();
const response = await next();
const duration = Date.now() - start;
console.log(`Request completed in ${duration}ms with status ${response.statusCode}`);
return response;
});
// Define a auth middleware
const requireAuth = async (c, next) => {
const token = c.headers.authorization?.replace('Bearer ', '');
if (!token) {
return c.status(401).json({
error: 'Unauthorized',
message: 'Authentication required'
});
}
// Validate token logic would go here
// For this example, we'll just check if it's "valid-token"
if (token !== 'valid-token') {
return c.status(401).json({
error: 'Unauthorized',
message: 'Invalid authentication token'
});
}
// Set user on context for downstream handlers
c.user = { id: '123', name: 'Example User' };
return next();
};
// Basic routes
api.get('/', (c) => c.text('Hello, World!'));
api.get('/json', (c) => c.json({ message: 'This is JSON' }));
// Route with URL parameters
api.get('/users/:id', (c) => {
return c.json({
userId: c.params.id,
message: `User details for ${c.params.id}`
});
});
// Route with query parameters
api.get('/search', (c) => {
const query = c.query.q || '';
const page = Number.parseInt(c.query.page || '1');
return c.json({
query,
page,
results: [`Result 1 for "${query}"`, `Result 2 for "${query}"`]
});
});
// POST route with body parsing
api.post('/echo', (c) =>
c.json({
message: 'Echo response',
body: c.body,
contentType: c.req.headers['content-type']
})
);
// Route with middleware
api.get('/protected', requireAuth, (c) => {
return c.json({
message: 'This is protected content',
user: c.user
});
});
// Route with custom status code
api.get('/not-found', (c) =>
c.status(404).json({
error: 'Not Found',
message: 'This resource was not found'
})
);
// Route with redirection
api.get('/redirect', (c) => c.redirect('/redirected-to'));
api.get('/redirected-to', (c) => c.text('You have been redirected'));
// Error handling example
api.get('/error', () => {
throw new Error('This is a test error');
});
api.start();Binary File Uploads
MikroServe automatically handles binary file uploads by preserving the raw Buffer for binary content types.
import { MikroServe, type Context } from 'mikroserve';
import { writeFile } from 'node:fs/promises';
const api = new MikroServe();
// Handle binary file upload (e.g., image, PDF, video)
api.post('/upload', async (c: Context) => {
const buffer = c.body as Buffer;
// Save to disk
await writeFile('/uploads/file.bin', buffer);
return c.json({
success: true,
size: buffer.length,
message: 'File uploaded successfully'
});
});
// Handle image upload with binary response
api.post('/process-image', async (c: Context) => {
const imageBuffer = c.body as Buffer;
// Process the image (resize, compress, etc.)
const processedImage = await processImage(imageBuffer);
// Return binary image
return c.binary(processedImage, 'image/jpeg');
});
api.start();Supported binary content types:
- Images:
image/*(PNG, JPEG, GIF, WebP, etc.) - Videos:
video/*(MP4, WebM, etc.) - Audio:
audio/*(MP3, WAV, etc.) - Documents:
application/pdf, Office documents, etc. - Archives:
application/zip,application/gzip, etc. - Generic:
application/octet-stream
Multipart Form-Data (File Uploads from HTML Forms)
Handle file uploads from HTML forms with automatic multipart parsing.
import { MikroServe, type Context, type MultipartFormData } from 'mikroserve';
const api = new MikroServe();
// Handle HTML form with file upload
api.post('/submit-form', async (c: Context) => {
const { fields, files } = c.body as MultipartFormData;
// Access form fields
console.log(fields.username); // "john_doe"
console.log(fields.email); // "[email protected]"
// Access uploaded file
const uploadedFile = files.avatar;
console.log(uploadedFile.filename); // "profile.jpg"
console.log(uploadedFile.contentType); // "image/jpeg"
console.log(uploadedFile.size); // 45234
// uploadedFile.data is a Buffer
await saveFile(`/uploads/${uploadedFile.filename}`, uploadedFile.data);
return c.json({
success: true,
user: fields.username,
file: uploadedFile.filename
});
});
// Handle multiple file uploads
api.post('/upload-multiple', async (c: Context) => {
const { fields, files } = c.body as MultipartFormData;
// Multiple files with same field name become an array
const uploadedFiles = files.documents as Array<MultipartFile>;
for (const file of uploadedFiles) {
await saveFile(`/uploads/${file.filename}`, file.data);
}
return c.json({
success: true,
count: uploadedFiles.length
});
});
api.start();Example HTML form:
<form action="http://localhost:3000/submit-form" method="POST" enctype="multipart/form-data">
<input type="text" name="username" value="john_doe">
<input type="email" name="email" value="[email protected]">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>Custom Configuration
Configure body size limits, request timeouts, and more.
import { MikroServe } from 'mikroserve';
const api = new MikroServe({
port: 3000,
host: '0.0.0.0',
// Set maximum request body size (default: 1MB)
maxBodySize: 50 * 1024 * 1024, // 50MB for large file uploads
// Set request timeout to prevent slow clients (default: 30 seconds)
requestTimeout: 60000, // 60 seconds, set to 0 to disable
// Rate limiting
rateLimit: {
enabled: true,
requestsPerMinute: 100
},
// CORS configuration
allowedDomains: ['https://yourdomain.com', 'https://app.yourdomain.com'],
// Enable debug logging
debug: false
});
api.start();Configuration
All of the settings already presented in the above examples can be provided in multiple ways.
- They can be provided via the CLI, e.g.
node app.js --port 1234. - Certain values can be provided via environment variables.
- Port:
process.env.PORT- number - Host:
process.env.HOST- string - Debug:
process.env.DEBUG- boolean
- Port:
- Programmatically/directly via scripting, e.g.
new MikroServe({ port: 1234 }). - They can be placed in a configuration file named
mikroserve.config.json(plain JSON), which will be automatically applied on load.
Options
| Option | Type | Default | Description |
|------------------------|-----------|--------------|-------------------------------------------------------|
| port | number | 3000 | Port to listen on |
| host | string | '0.0.0.0' | Host address to bind to |
| useHttps | boolean | false | Enable HTTPS |
| useHttp2 | boolean | false | Enable HTTP/2 |
| sslCert | string | '' | Path to SSL certificate file |
| sslKey | string | '' | Path to SSL key file |
| sslCa | string | '' | Path to SSL CA file |
| debug | boolean | false | Enable debug logging |
| maxBodySize | number | 1048576 | Maximum request body size in bytes (1MB default) |
| requestTimeout | number | 30000 | Request timeout in milliseconds (30s default, 0 = disabled) |
| rateLimit.enabled | boolean | true | Enable rate limiting |
| rateLimit.requestsPerMinute | number | 100 | Maximum requests per minute per IP |
| allowedDomains | string[]| ['*'] | CORS allowed domains (wildcard * allows all) |
CLI Arguments
| CLI argument | CLI value | JSON (config file) value | Environment variable |
|--------------|-----------------------------|-----------------------------|----------------------|
| --port | <number> | port | PORT |
| --host | <string> | host | HOST |
| --https | none (is flag) | useHttps | |
| --http2 | none (is flag) | useHttp2 | |
| --cert | <string> | sslCert | |
| --key | <string> | sslKey | |
| --ca | <string> | sslCa | |
| --ratelimit | none (is flag) | rateLimit.enabled | |
| --rps | <number> | rateLimit.requestsPerMinute | |
| --allowed | <comma-separated strings> | allowedDomains | |
| --debug | none (is flag) | debug | DEBUG |
Order of Application
As per MikroConf behavior, the configuration sources are applied in this order:
- Command line arguments (highest priority)
- Programmatically provided config
- Config file (JSON)
- Default values (lowest priority)
Create self-signed HTTPS certificates
On Mac and Linux, run:
openssl req -x509 -newkey rsa:2048 -keyout local-key.pem -out local-cert.pem -days 365 -nodes -subj "/CN=localhost"Feel free to change the key and cert names as you wish.
Response Helpers
MikroServe provides convenient response helpers on the context object:
// Text response
c.text('Plain text content', 200);
// JSON response
c.json({ message: 'Success' }, 200);
// HTML response
c.html('<h1>Hello World</h1>', 200);
// Binary response (for files, images, etc.)
c.binary(buffer, 'image/png', 200);
// Form-urlencoded response
c.form({ key: 'value' }, 200);
// Redirect
c.redirect('/new-location', 302);
// Custom status code
c.status(404).json({ error: 'Not Found' });
// Access raw response object
const res = c.raw();TypeScript Support
MikroServe is written in TypeScript and exports all necessary types:
import {
MikroServe,
type Context,
type MultipartFile,
type MultipartFormData
} from 'mikroserve';
// Context provides full type information for request/response
api.post('/upload', async (c: Context) => {
// c.body, c.params, c.query, c.headers are all typed
const data = c.body as MultipartFormData;
// ...
});Performance Tips
Adjust body size limits based on your use case:
- For JSON APIs: Keep default 1MB or lower
- For file uploads: Increase to 50-100MB or as needed
Enable request timeouts to prevent resource exhaustion:
- Default 30 seconds is good for most APIs
- Increase for large file uploads
- Set to 0 only if you have other timeout mechanisms
Use rate limiting to protect against abuse:
- Default 100 requests/minute per IP
- Adjust based on your traffic patterns
Binary responses: Use
c.binary()for files to avoid JSON serialization overhead
License
MIT. See the LICENSE file.
