DEV Community

Mayank Thakur
Mayank Thakur

Posted on

mimi.js vs Express: 4 Faster, Built-in Auth & Zero Config — Benchmarks Inside

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

And a mimi.js project:

"mimi.js"
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode
node index.js
Enter fullscreen mode Exit fullscreen mode

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 });
};
Enter fullscreen mode Exit fullscreen mode

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([]));
Enter fullscreen mode Exit fullscreen mode

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()));
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 }));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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);
Enter fullscreen mode Exit fullscreen mode

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)