Compared honestly.
Sources you can check.
Everyone evaluating MoroJS asks the same thing: why not just use Express, Fastify, NestJS, or Hono? Fair question. So here's the same endpoint written in each one, a feature-by-feature checklist, and benchmarks where every framework runs head-to-head in one suite - same machine, same session. Run it yourself and check.
Every Node framework makes 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.
- Express
- Designed in 2010, with no types, no validation and no async-aware error handling. Every project ends up rebuilding the same handful of pieces.
- Fastify
- Fast, but you assemble the framework yourself: schemas, plugins, decorators, lifecycle hooks.
- NestJS
- Powerful, but heavy. Decorators, modules, providers, DI container. Java in TypeScript clothing.
- Hono / Elysia
- Small and fast, and that is the whole offer. Auth, validation, websockets and gRPC are yours to source, wire up and keep working.
MoroJS ships with all of it. You use only what you need.
Less code. More guarantees.
The same validated POST endpoint in four frameworks. Identical behavior, wildly different effort.
import express from 'express'import { z } from 'zod' const app = express()app.use(express.json()) const CreateUser = z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().min(18),}) app.post('/users', (req, res, next) => { const parsed = CreateUser.safeParse(req.body) if (!parsed.success) { return res.status(400).json({ error: 'VALIDATION', issues: parsed.error.issues, }) } // req.body is still 'any' to TS const user = { id: crypto.randomUUID(), ...parsed.data } res.json(user)}) app.use((err, req, res, next) => { res.status(500).json({ error: 'INTERNAL' })}) app.listen(3000)
- Manual body parsing setup
- Hand-rolled validation in every handler
- No type inference on req.body
- Manual error middleware required
import Fastify from 'fastify'import { z } from 'zod'import { serializerCompiler, validatorCompiler, ZodTypeProvider,} from 'fastify-type-provider-zod' const app = Fastify().withTypeProvider<ZodTypeProvider>()app.setValidatorCompiler(validatorCompiler)app.setSerializerCompiler(serializerCompiler) app.post('/users', { schema: { body: z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().min(18), }), }, handler: (req, reply) => { return { id: crypto.randomUUID(), ...req.body } },}) app.listen({ port: 3000 })
- Requires type-provider plugin
- Compiler setup boilerplate
- Schema lives separately from handler
- Plugin ecosystem for everything
// users.dto.tsimport { z } from 'zod'import { createZodDto } from 'nestjs-zod'export class CreateUserDto extends createZodDto( z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().min(18), }),) {} // users.controller.tsimport { Body, Controller, Post } from '@nestjs/common'@Controller('users')export class UsersController { @Post() create(@Body() body: CreateUserDto) { return { id: crypto.randomUUID(), ...body } }} // users.module.ts@Module({ controllers: [UsersController] })export class UsersModule {} // app.module.ts + main.ts also required...
- 4+ files for one endpoint
- Decorators, modules, providers, DI
- Heavy bootstrap, slow cold start
- Curve before you ship anything
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 in one chain
- req.body fully typed, already validated
- Errors handled by intelligent defaults
- No plugins. No boilerplate.
Everything in the box. Nothing to install.
Auth, validation, websockets, GraphQL, gRPC, HTTP/2, every one first-class and ready to use. No plugins to vet, no glue code to write.
| Feature | Express | Fastify | NestJS | Hono | MoroJS |
|---|---|---|---|---|---|
| Core | |||||
| TypeScript native | Not supported | Via plugin / community | Built-in | Built-in | Built-in |
| Zero-config | Via plugin / community | Not supported | Not supported | Via plugin / community | Built-in |
| Intelligent middleware ordering | Not supported | Not supported | Not supported | Not supported | Built-in |
| Multi-runtime (Node/Edge/Lambda/Workers) | Not supported | Not supported | Not supported | Built-in | Built-in |
| Validation | |||||
| Zod / Joi / Yup support | Via plugin / community | Via plugin / community | Via plugin / community | Via plugin / community | Built-in |
| Type inference from schema | Not supported | Via plugin / community | Via plugin / community | Via plugin / community | Built-in |
| Auth | |||||
| Built-in OAuth providers | Not supported | Not supported | Via plugin / community | Not supported | Built-in |
| RBAC + sessions, zero deps | Not supported | Not supported | Via plugin / community | Not supported | Built-in |
| Real-time | |||||
| WebSockets | Via plugin / community | Via plugin / community | Built-in | Via plugin / community | Built-in |
| Server-Sent Events | Via plugin / community | Via plugin / community | Built-in | Built-in | Built-in |
| GraphQL | Via plugin / community | Via plugin / community | Built-in | Via plugin / community | Built-in |
| gRPC | Via plugin / community | Via plugin / community | Built-in | Not supported | Built-in |
| HTTP/2 native | Not supported | Built-in | Built-in | Via plugin / community | Built-in |
| Background work | |||||
| Built-in job scheduler | Not supported | Not supported | Via plugin / community | Not supported | Built-in |
| Worker threads facade | Not supported | Not supported | Via plugin / community | Not supported | Built-in |
| Mail (SES, SendGrid, Resend) | Not supported | Not supported | Via plugin / community | Not supported | Built-in |
| Performance | |||||
| Own native engine (default) | Not supported | Not supported | Not supported | Not supported | Built-in |
| Built-in clustering | Not supported | Not supported | Not supported | Not supported | Built-in |
| req/sec (peak, same-session suite) | 66k | 109k | n/a | 108k | 742k |
Up to 741,719 req/sec. Reproduce it yourself.
Nothing here is borrowed from someone else's numbers. Every framework was measured in the same suite, on the same machine, in the same session, with the same tool. These are the real-world numbers, no pipelining; the pipelined ×10 microbench peaks at 741,719 req/s clustered. Full methodology is one click away.
wrk -c100 -d40s · August 2026 · full methodology at /benchmarks · source: Moro-JS/benchmark ↗
- +5.5%
- vs the uWS adapter · 108,687 vs 103,061 req/s
- 1.6×
- vs Fastify · 108,687 vs 68,880 req/s
- 1.8×
- vs Koa · 108,687 vs 59,697 req/s
- 2.4×
- vs Express · 108,687 vs 45,976 req/s
Build it in the next 60 seconds.
One command. A typed, validated, production-ready API. No framework decisions left to make.
MIT licensed · No telemetry · Open governance