@exocore/multi
v1.2.0
Published
Lightweight HTTP framework with logger, fetch wrapper, rate limiter, middleware, and interactive CLI
Maintainers
Readme
@exocore/multi
Lightweight, zero-dependency Node.js HTTP framework with a gradient logger, fetch wrapper, rate limiter, cookie jar, and full middleware suite — built for people who want Express-like DX without the weight.
npm install @exocore/multiTable of Contents
Quick Start
import { server, logger, http } from '@exocore/multi';
const app = server.create({
port: 3000,
prettyPrint: true,
jiglet: 'MY APP',
cors: true,
helmet: true,
bodyParser: true,
cookieParser: true,
compression: true,
rateLimit: { enabled: true, windowMs: 900000, max: 100 },
});
app.get('/hello', (req, res) => {
res.json(http.ok({ message: 'Hello world!' }));
});
app.listen(3000, () => {
logger.info('Server ready');
});server
server.create(config)
Creates and returns an ExoServer instance.
| Option | Type | Default | Description |
|---|---|---|---|
| port | number | 3000 | Port the server listens on |
| prettyPrint | boolean | true | Gradient HTTP request logs in the terminal |
| jiglet | string | null | Prints a gradient banner box on startup |
| cors | boolean | false | Adds Access-Control-Allow-* headers |
| helmet | boolean | false | Sets secure HTTP headers |
| bodyParser | boolean | true | Auto-parses JSON and plain-text bodies |
| cookieParser | boolean | true | Parses Cookie header into req.cookies |
| compression | boolean | false | Brotli/gzip/deflate response compression |
| rateLimit | object \| null | null | In-memory per-IP rate limiting |
rateLimit options:
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Toggle rate limiting on/off |
| windowMs | number | 900000 | Window size in milliseconds (15 min) |
| max | number | 100 | Max requests per IP per window |
| message | string | 'Too many requests' | Message returned on 429 |
Routes
// Named URL params
app.get('/users/:id', (req, res) => {
res.json(http.ok({ id: req.params.id }));
});
// Query string — req.query is auto-parsed
app.get('/search', (req, res) => {
res.json(http.ok({ q: req.query.q }));
});
// POST with body (auto-parsed when bodyParser: true)
app.post('/data', (req, res) => {
res.json(http.ok(req.body));
});
// All methods
app.put('/item/:id', handler);
app.patch('/item/:id', handler);
app.delete('/item/:id', handler);
app.all('/any', handler); // matches every HTTP methodMiddleware
// Custom middleware
app.use((req, res, next) => {
req.startTime = Date.now();
next();
});
// Use any middleware factory
import { cors } from '@exocore/multi';
app.use(cors.middleware({ origin: 'https://mysite.com' }));app.listen(port?, callback?)
Starts the HTTP server. If jiglet is configured, the banner prints before the first log line.
app.listen(3000, () => console.log('up'));
// or rely on the port set in server.create()
app.listen();app.close(callback?)
Gracefully shuts down the server.
logger
A single shared ExoLogger instance with ANSI RGB gradient output. Every log line is prefixed with [ EXOCORE ] > in a cyan-blue gradient.
import { logger } from '@exocore/multi';Methods
| Method | Color gradient | Description |
|---|---|---|
| logger.info(msg, meta?) | Cyan → teal | General information |
| logger.success(msg, meta?) | Green → lime | Success/completion messages |
| logger.warn(msg, meta?) | Amber → orange | Warnings |
| logger.error(msg, meta?) | Red → red | Errors (also prints stack if meta is an Error) |
| logger.debug(msg, meta?) | Purple → violet | Debug output |
| logger.custom(msg, opts?) | Custom gradient | Custom colors via { from, to } |
| logger.request(method, path, status, ms) | Status-aware | HTTP access log (green 2xx, amber 4xx, red 5xx) |
| logger.jiglet(text) | Cyan → blue | Prints a gradient box banner |
| logger.resetGreeting() | — | Re-arms the one-time startup greeting |
logger.info('Server started');
logger.success('User created', { id: 42 });
logger.warn('Cookie expiring soon');
logger.error('DB connection failed', new Error('ECONNREFUSED'));
logger.debug('Token payload', { sub: 'user_1' });
// Custom gradient
logger.custom('Deployed!', { from: '#ff00cc', to: '#3333ff' });
// Banner on startup
logger.jiglet('EXOCORE');
// ╔══════════════╗
// ║ EXOCORE ║
// ╚══════════════╝
// HTTP request log (called automatically when prettyPrint: true)
logger.request('GET', '/api/health', 200, 3);
// [ EXOCORE ] > GET /api/health → 200 (3ms)logger.use(opts)
Configures global logger behaviour. Returns the logger for chaining.
logger.use({
timestamp: false, // disable timestamp prefix
bold: false, // disable bold prefix
highlight: false, // disable gradient colouring on the message itself
greetings: [ // replace the random startup greeting pool
'Ready!',
'Online ⚡',
],
});http
Utility object for consistent API responses, status codes, MIME types, and query string handling.
import { http } from '@exocore/multi';Response helpers
// Success envelope → { success: true, data: ... }
res.json(http.ok({ user: { id: 1, name: 'Franz' } }));
// Error envelope → { success: false, error: '...', code?: ... }
res.status(400).json(http.err('Invalid email'));
res.status(422).json(http.err('Validation failed', 'INVALID_FIELDS'));Pagination
const page = Number(req.query.page ?? 1);
const limit = Number(req.query.limit ?? 10);
const result = http.paginate(allUsers, page, limit);
// {
// items: [...],
// total: 250,
// page: 1,
// limit: 10,
// pages: 25,
// }
res.json(http.ok(result));Status codes & MIME types
http.status.OK // 200
http.status.CREATED // 201
http.status.NO_CONTENT // 204
http.status.BAD_REQUEST // 400
http.status.UNAUTHORIZED // 401
http.status.FORBIDDEN // 403
http.status.NOT_FOUND // 404
http.status.TOO_MANY_REQUESTS // 429
http.status.INTERNAL_SERVER_ERROR // 500
http.mime.json // 'application/json'
http.mime.html // 'text/html'
http.mime.text // 'text/plain'
http.mime.stream // 'application/octet-stream'
http.mime.svg // 'image/svg+xml'Query string helpers
// Parse a raw query string
const params = http.parseQuery('?page=2&limit=20');
// { page: '2', limit: '20' }
// Build a query string
const qs = http.buildQuery({ page: 2, limit: 20 });
// '?page=2&limit=20'fetch
A zero-dependency HTTP client built on Node's http/https modules.
import { fetch } from '@exocore/multi';// GET
const res = await fetch.get('https://api.example.com/users');
const users = res.json();
// POST
const res = await fetch.post('https://api.example.com/users', {
name: 'Franz',
email: '[email protected]',
});
console.log(res.status, res.ok);
// PUT / PATCH / DELETE
await fetch.put('https://api.example.com/users/1', { name: 'Updated' });
await fetch.patch('https://api.example.com/users/1', { name: 'Patched' });
await fetch.delete('https://api.example.com/users/1');
// Custom request with headers and timeout
const res = await fetch.request('https://api.example.com/secure', {
method: 'POST',
headers: { Authorization: 'Bearer <token>' },
body: { key: 'value' },
timeout: 5000,
});Response shape:
{
status: number;
headers: Record<string, string>;
body: string;
ok: boolean; // true for 2xx
json(): unknown; // parses body as JSON
}cookieJar
A shared in-memory cookie store. Useful for managing session cookies across outgoing requests.
import { cookieJar } from '@exocore/multi';// Load a batch of cookies
cookieJar.load({ SID: 'abc123', HSID: 'xyz' });
// Individual operations
cookieJar.set('token', 'mytoken');
cookieJar.get('token'); // 'mytoken'
cookieJar.has('token'); // true
cookieJar.delete('token');
// Get all as a plain object
cookieJar.getAll(); // { SID: 'abc123', HSID: 'xyz' }
// Get as a Cookie: header string for a URL
const header = cookieJar.getForUrl('https://example.com');
// 'SID=abc123; HSID=xyz'
// Save cookies from a Set-Cookie response header
cookieJar.saveFromResponse(url, res.headers['set-cookie']);
cookieJar.size(); // number of stored cookies
cookieJar.clear(); // remove all cookiesrate
Standalone rate limiter middleware factory — use this when you want per-route rate limiting instead of server-wide.
import { rate } from '@exocore/multi';// Create a middleware
const apiLimit = rate.create({
windowMs: 60 * 1000, // 1 minute
max: 20,
message: 'Slow down!',
});
app.use(apiLimit);
// Or apply only to specific routes by composing manually
app.post('/api/login', (req, res, next) => {
apiLimit(req, res, next);
}, handler);Rate limit response headers automatically set:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 18
X-RateLimit-Reset: 1716000000000Middleware
All middleware can be used standalone or passed to app.use().
cors
import { cors } from '@exocore/multi';
// Simple — allow all origins
app.use(cors.middleware());
// Restricted
app.use(cors.middleware({
origin: 'https://mysite.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
}));
// Just get the headers as an object (no middleware)
const headers = cors.headers({ origin: 'https://mysite.com' });helmet
Sets secure HTTP response headers.
import { helmet } from '@exocore/multi';
app.use(helmet.middleware());
// Custom CSP
app.use(helmet.middleware({
contentSecurityPolicy: "default-src 'self'; img-src *",
xFrameOptions: 'DENY',
referrerPolicy: 'strict-origin',
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
}));Headers set by default:
X-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINContent-Security-Policy: default-src 'self'Referrer-Policy: no-referrerStrict-Transport-Security: max-age=31536000; includeSubDomainsX-Permitted-Cross-Domain-Policies: noneX-Download-Options: noopenX-XSS-Protection: 0Permissions-Policy: geolocation=(), microphone=(), camera=()
bodyParser
import { bodyParser } from '@exocore/multi';
// JSON bodies (default limit 1 MB)
app.use(bodyParser.json({ limit: 2 * 1024 * 1024 })); // 2 MB
// Plain text
app.use(bodyParser.text());
// URL-encoded form data
app.use(bodyParser.urlencoded());When bodyParser: true is set in server.create(), JSON and plain-text are parsed automatically for all non-GET/HEAD requests.
cookieParser
import { cookieParser, serializeCookie } from '@exocore/multi';
app.use(cookieParser.middleware());
// Read cookies
app.get('/me', (req, res) => {
const token = req.cookies.token;
res.json(http.ok({ token }));
});
// Set a cookie
app.post('/login', (req, res) => {
const cookie = serializeCookie('token', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'Lax',
maxAge: 86400,
path: '/',
});
res.setHeader('Set-Cookie', cookie);
res.json(http.ok({ ok: true }));
});
// Parse a cookie string manually
const parsed = cookieParser.parse('SID=abc; HSID=xyz');
// { SID: 'abc', HSID: 'xyz' }compression
Automatically compresses responses with Brotli, gzip, or deflate based on the client's Accept-Encoding header.
import { compression } from '@exocore/multi';
app.use(compression.middleware());
// Custom threshold and compression level
app.use(compression.middleware({
threshold: 2048, // only compress if response > 2 KB (default 1 KB)
level: 6, // zlib compression level (1–9)
}));core
Functional utility helpers.
import { core } from '@exocore/multi';// compose — right-to-left function composition
const process = core.compose(trim, lowercase, sanitize);
const result = process(input);
// pipe — left-to-right
const process = core.pipe(sanitize, lowercase, trim);
const result = process(input);
// memoize — cache function results by arguments
const cached = core.memoize(expensiveFunction);
// debounce — delay execution
const debouncedSave = core.debounce(save, 300);
// throttle — limit call rate
const throttledLog = core.throttle(log, 1000);
// sleep — async delay
await core.sleep(500);
// retry — retry async function with exponential backoff
const data = await core.retry(() => fetch.get(url), 3, 500);
// attempts: 3, initial delay: 500ms → 500ms, 1000ms, 2000ms
// chunk — split array into pages
core.chunk([1,2,3,4,5], 2); // [[1,2],[3,4],[5]]
// deepClone
const copy = core.deepClone(obj);
// omit / pick
const safe = core.omit(user, 'password', 'token');
const view = core.pick(user, 'id', 'name', 'email');greetUser
A simple localised greeting helper.
import { greetUser } from '@exocore/multi';
greetUser('Franz');
// 'Hello, Franz! Welcome to @exocore/multi.'
greetUser({ name: 'Franz', title: 'Dr.', lang: 'ja' });
// 'Konnichiwa, Dr. Franz! Welcome to @exocore/multi.'
// Supported langs: en, tl (Filipino), ja, esLicense
MIT — made with ☕ by CHORU
