Every Node.js project I've worked on starts with the same ritual:
npm install express cors helmet morgan body-parser bcryptjs jsonwebtoken mongoose
npm install -D @types/express @types/cors @types/bcryptjs @types/jsonwebtoken
Then 40 minutes of wiring. A cors() call here, a body-parser config there, a JWT middleware you copy-paste from your last project. By the time you write your first route, you've already written a hundred lines that have nothing to do with your actual problem.
I got tired of it. So I built mimi.js — a Node.js framework that ships with everything a real API actually needs, keeps the Express API you already know, and runs at near-Fastify speed.
Here's what the same setup looks like with mimi.js:
npm install mimi.js
That's it.
The Benchmark Numbers
Before I walk through the features, let's talk performance — because "Express-compatible" historically meant slow.
I ran autocannon against Express 4, Fastify 5, and mimi.js v2 at 100 concurrent connections for 10 seconds each, across three scenarios: a simple route, a route with URL parameters, and an app with 50 registered routes.
| Express 4 | Fastify 5 | mimi.js v2 | |
|---|---|---|---|
Simple GET /
|
20,414 req/s | 94,060 req/s | 89,504 req/s |
GET /user/:id |
19,934 req/s | 93,716 req/s | 87,270 req/s |
| 50-route app | 19,704 req/s | 94,275 req/s | 88,305 req/s |
| Memory (RSS) | 136 MB | 93 MB | 96 MB |
| p99 latency | 8–9 ms | 2 ms | 2 ms |
4.4× faster than Express on every scenario. Within 5% of Fastify. Memory overhead vs Fastify: 3 MB.
The 50-route number is the one worth noting. Most Express alternatives show great numbers on hello-world and then slow down as your app grows — because they use a linear router that checks each route one by one. mimi.js uses find-my-way, the same radix trie engine Fastify uses, so route lookup is O(path depth) regardless of how many routes you register. The 50-route result (88,305 req/s) is essentially the same as the simple-route result (89,504 req/s). It stays flat.
You can run these benchmarks yourself — the source is in the repo.
What Ships Out of the Box
Here's the dependency list of a typical production Express API:
"express", "cors", "helmet", "morgan", "body-parser",
"bcryptjs", "jsonwebtoken", "mongoose",
"@types/express", "@types/cors", "@types/bcryptjs", "@types/jsonwebtoken"
And a mimi.js project:
"mimi.js"
Everything is included: JWT auth, bcrypt password hashing, MongoDB and SQLite adapters, CORS, security headers, structured logging, static file serving, auto Swagger docs, and body parsing — shipped as a single package with first-party TypeScript declarations.
A Real App in Under 30 Lines
Here's a protected API with JWT auth, request logging, and CORS:
// index.js
const { mimi, json, cors, requestLogger, generateToken, hashPassword, authMiddleware } = require('mimi.js');
const app = mimi();
app.use(requestLogger);
app.use(json());
app.use(cors());
app.post('/register', async (req, res) => {
const { email, password } = req.body;
const hash = await hashPassword(password);
// save { email, hash } to your DB
res.status(201).json({ message: 'Account created' });
});
app.post('/login', async (req, res) => {
// fetch user, verify password, then:
const token = generateToken({ id: user.id, email: user.email });
res.json({ token });
});
app.get('/me', authMiddleware, (req, res) => {
res.json({ user: req.user }); // decoded JWT payload, verified automatically
});
app.listen(3000, () => console.log('http://localhost:3000'));
node index.js
No ts-node. No build step for development. Just node.
With Express, this same app requires installing and configuring four separate packages, hunting down TypeScript types for each, and writing a verifyToken middleware from scratch. With mimi.js, you import what you need and start.
TypeScript Without the Ceremony
Express's TypeScript story involves @types/express from DefinitelyTyped — a community-maintained package that often lags the library and types req.body as any.
mimi.js ships its own .d.ts declarations. Every exported function, every request property, fully typed:
import { mimi, RequestHandler } from 'mimi.js';
const getUser: RequestHandler = (req, res) => {
const id: string = req.params.id; // string, not any
res.json({ id });
};
No separate types package. No version mismatch between the library and its types.
Auto Swagger Docs
Add JSDoc to your routes, call setupSwagger, and get interactive API documentation at /api-docs automatically:
import { mimi, json, setupSwagger } from 'mimi.js';
const app = mimi();
app.use(json());
setupSwagger(app, {
info: { title: 'My API', version: '1.0.0' },
filesPattern: './routes/**/*.js',
baseDir: __dirname,
});
/**
* @openapi
* /users:
* get:
* summary: Get all users
* responses:
* 200:
* description: A list of users
*/
app.get('/users', (req, res) => res.json([]));
The OpenAPI spec is also available as JSON at /api-docs/swagger.json — useful for generating client SDKs.
Database Adapters
MongoDB
import { mimi, mongodbManager } from 'mimi.js';
import mongoose from 'mongoose';
const db = new mongodbManager(); // singleton — safe to call anywhere
await db.connect('mongodb://localhost:27017/myapp');
const User = mongoose.model('User', new mongoose.Schema({ email: String }));
app.get('/users', async (req, res) => res.json(await User.find()));
SQLite
import { SQLiteManager } from 'mimi.js';
import { DataTypes } from 'sequelize';
const db = new SQLiteManager({ storage: './dev.sqlite' });
await db.connect();
const User = db.instance.define('User', { email: DataTypes.STRING });
await User.sync();
Both adapters are opt-in — if you don't use them, they don't add to your startup cost.
Express Middleware Just Works
mimi.js patches http.IncomingMessage and http.ServerResponse with the same properties Express does, so existing Express middleware works without modification:
const rateLimit = require('express-rate-limit');
app.use(rateLimit({ windowMs: 60_000, max: 100 }));
This means you can migrate an existing Express app incrementally — swap the framework, keep every middleware, move routes one at a time.
vs Express vs Fastify — Honest Take
vs Express: mimi.js runs 4× faster and removes all the boilerplate setup. The API is intentionally compatible, so migration is low-friction. If you're starting a new Node.js project or frustrated with Express's "install everything yourself" model, mimi.js is the faster, leaner version of the thing you already know.
vs Fastify: Fastify is a brilliant piece of engineering. If you need scoped plugin isolation, schema-based JSON serialization via fast-json-stringify, or Fastify's 9-hook lifecycle system, use Fastify. mimi.js sits at a different point in the tradeoff space: near-Fastify performance, Express-compatible API surface, and a batteries-included feature set Fastify doesn't ship by default.
Try It
npm install mimi.js
const { mimi, json, cors, security } = require('mimi.js');
const app = mimi();
app.use(json());
app.use(cors());
app.use(security());
app.get('/', (req, res) => res.json({ hello: 'world' }));
app.listen(3000);
PRs and feedback welcome. If you hit something that doesn't work exactly like Express, that's a bug — open an issue.
Top comments (0)