200k+ req/sec.Zero config.Every runtime.
Build production APIs with intelligent routing, end-to-end type safety, and built-in everything. Deploy the same code to Node, Edge, Lambda, or Workers.
Every Node frameworkmakes you choose.
Speed or features. Simplicity or power. Type-safety or flexibility. You wire middleware in the right order and pray nothing breaks at 3am.
Designed in 2010. No types, no validation, no async-aware errors. Every project rebuilds the same wheels.
Fast, but you assemble the framework yourself — schemas, plugins, decorators, lifecycle hooks.
Powerful, but heavy. Decorators, modules, providers, DI container — Java in TypeScript clothing.
Minimal and fast — but minimal. You still need auth, validation, websockets, gRPC. Plug and pray.
One framework.Everything included.
Routing, validation, auth, real-time, multi-runtime — every primitive you need, with the ergonomics you want.
Schema-first routing in 6 lines
Validate, type, document, and handle a route in one chain. No middleware files, no DTOs, no decorators.
- Type inference
req.bodyandreq.queryare fully typed from your schema. - Auto-validation400 with a structured error before your handler ever runs.
- Universal validatorsSwap Zod for Joi, Yup, or Class Validator without rewriting.
import { createApp, z } from '@morojs/moro'
const app = await createApp()
app.post('/users')
.body(z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(18),
}))
.handler((req) => {
// req.body is fully typed and already validated
return { id: crypto.randomUUID(), ...req.body }
})
app.listen(3000)Middleware thatorders itself.
Add cors, auth, rate-limit, and validation in any order. MoroJS analyzes dependencies and runs them in the correct sequence — every time.
Less code.More guarantees.
One validated POST endpoint, in four frameworks. Same behavior — radically different effort.
import { createApp, z } from '@morojs/moro'
const app = await createApp()
app.post('/users')
.body(z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(18),
}))
.handler((req) => {
return { id: crypto.randomUUID(), ...req.body }
})
app.listen(3000)- Schema, validation, types — one chain
- req.body fully typed, already validated
- Errors handled by intelligent defaults
- No plugins. No boilerplate.
Up to 226,253 req/sec.Reproduce it yourself.
Every number below is sourced — pulled straight from each framework's own published benchmarks. Same autocannon profile. Click source ↗ on any row.
Same autocannon profile (-c100 -d40 -p10). Express, Koa, Hono, Fastify from fastify/benchmarks (Apr 2026, Node 24). NestJS (Fastify adapter) from Sharkbench (Aug 2025, Node 22). Elysia from APIScout 2026 on Bun. MoroJS variants from the published suite on M2 Ultra.
Full methodologyEverything in the box.Nothing to install.
Auth, validation, websockets, GraphQL, gRPC, HTTP/2 — all first-class. No plugin ecosystem to navigate.
| Feature | Express | Fastify | NestJS | Hono | MoroJS |
|---|---|---|---|---|---|
| Core | |||||
| TypeScript native | |||||
| Zero-config | |||||
| Intelligent middleware ordering | |||||
| Multi-runtime (Node/Edge/Lambda/Workers) | |||||
| Validation | |||||
| Zod / Joi / Yup support | |||||
| Type inference from schema | |||||
| Auth | |||||
| Built-in OAuth providers | |||||
| RBAC + sessions, zero deps | |||||
| Real-time | |||||
| WebSockets | |||||
| Server-Sent Events | |||||
| GraphQL | |||||
| gRPC | |||||
| HTTP/2 native | |||||
| Background work | |||||
| Built-in job scheduler | |||||
| Worker threads facade | |||||
| Mail (SES, SendGrid, Resend) | |||||
| Performance | |||||
| uWebSockets transport | |||||
| Built-in clustering | |||||
| req/sec (peak, MoroJS suite) | 28k | 46k | 22k | n/a | 226k |
Same code.Every runtime.
Your business logic does not care where it runs. Switch from Node to Edge to Lambda by changing the entry file — never the routes.
// app.ts — your application code, identical in every runtime
import { createApp, z } from '@morojs/moro'
export const app = await createApp()
app.get('/users/:id')
.params(z.object({ id: z.string().uuid() }))
.handler((req) => {
return { id: req.params.id, name: `User ${req.params.id}` }
})
app.post('/users')
.body(z.object({ name: z.string(), email: z.string().email() }))
.handler((req) => ({ id: crypto.randomUUID(), ...req.body }))// server.ts
import { app } from './app'
app.listen(3000, () => {
console.log('Ready on :3000 — node runtime')
})Long-running server, full uWebSockets.js perf available.
Versioned modules.Built-in scheduler.
Drop a folder, get a versioned API surface. Schedule cron jobs, send mail, run workers — all from the same app instance.
// modules/users/index.ts — versioned, isolated, testable
import { defineModule } from '@morojs/moro'
import * as actions from './actions'
import * as schemas from './schemas'
export const usersModule = defineModule({
name: 'users',
version: '1.0.0',
routes: [
{
method: 'POST',
path: '/users',
handler: actions.createUser,
validation: { body: schemas.CreateUserSchema },
rateLimit: { requests: 10, window: 60_000 },
description: 'Create a new user',
},
],
})
// Mounted at /api/v1.0.0/users — versioning is automatic.// background work, in the same app
import { app, createApp } from '@morojs/moro'
// Cron expression
app.job('cleanup', '0 2 * * *', async () => {
await db.sessions.deleteExpired()
})
// Macro
app.job('daily-report', '@daily', async (ctx) => {
await mail.send({ subject: 'Daily report', html: report() })
})
// Interval string — '5m', '1h', '30s'
app.job('health-check', '5m', async (ctx) => {
ctx.logger.debug('alive', ctx.executionId)
})Auto-mounted at /api/v{version}/{module}.
Cron expressions, @daily macros, "5m" strings.
SES, SendGrid, Resend, Nodemailer — same API.
PostgreSQL, MySQL, MongoDB, SQLite, Redis, Drizzle.
Everything you need.Built in. Type-safe.
22 first-class features. No plugin ecosystem to navigate. No "what library do I use for X?". The answer is always: it is already there.
No install.Just run it.
A real MoroJS endpoint, executing right here. Change the input, hit run, see the response.
import { createApp, z } from '@morojs/moro'
const app = await createApp()
app.get('/hello')
.query(z.object({
name: z.string().optional().default('World'),
}))
.handler((req) => ({
message: `Hello, ${req.query.name}!`,
timestamp: new Date().toISOString(),
}))
app.listen(3000)Build it inthe next 60 seconds.
One command. A typed, validated, production-ready API. No framework decisions left to make.
MIT licensed · No telemetry · Open governance