DEV Community

Carlos Oliva Pascual
Carlos Oliva Pascual

Posted on • Originally published at stacknotice.com

Fastify Complete Guide: Fast Node.js APIs in 2026

Fastify is the fastest HTTP framework for Node.js — benchmarks show ~75k req/s vs ~35k for Express. Beyond speed, it has built-in JSON Schema validation, compiled response serialization, and a proper plugin system with encapsulation. All the things that require separate packages in Express.

Why Fastify Over Express

Express issues in 2026:

  • No built-in request validation — you add zod/joi manually
  • No schema-based response serialization — JSON.stringify every response
  • No TypeScript-first design — types via @types/express feel bolted on
  • Global middleware — no scoping, plugins interfere with each other

Fastify fixes all of these by default.

Installation

npm install fastify @sinclair/typebox
npm install -D @types/node typescript tsx
npm install @fastify/sensible @fastify/cors @fastify/jwt
Enter fullscreen mode Exit fullscreen mode

First Server

import Fastify from 'fastify'

const server = Fastify({ logger: true })

server.get('/', async () => {
  return { status: 'ok', timestamp: Date.now() }
})

server.get<{
  Params: { id: string }
}>('/users/:id', async (request) => {
  const { id } = request.params  // TypeScript knows id is string
  return { id, name: 'Alice' }
})

await server.listen({ port: 3000, host: '0.0.0.0' })
Enter fullscreen mode Exit fullscreen mode

No res.send() — just return the value. Fastify serializes it automatically.

Schema Validation with TypeBox

import { Type, Static } from '@sinclair/typebox'

const CreateUserBody = Type.Object({
  name: Type.String({ minLength: 1 }),
  email: Type.String({ format: 'email' }),
  role: Type.Union([Type.Literal('admin'), Type.Literal('user')], { default: 'user' })
})

const UserResponse = Type.Object({
  id: Type.String(),
  name: Type.String(),
  email: Type.String(),
  createdAt: Type.String()
})

type CreateUserBody = Static<typeof CreateUserBody>

server.post<{
  Body: CreateUserBody
  Reply: Static<typeof UserResponse>
}>('/users', {
  schema: {
    body: CreateUserBody,
    response: { 201: UserResponse }
  }
}, async (request, reply) => {
  const user = await db.user.create({ data: request.body })
  reply.status(201)
  return user  // validated + serialized against UserResponse
})
Enter fullscreen mode Exit fullscreen mode

What you get for free:

  • 400 with detailed error messages for invalid requests
  • Response serialization strips fields not in the schema (no accidental data leaks)
  • Type inference flows from schema to handler

Plugin System

// src/plugins/database.ts
import fp from 'fastify-plugin'
import { PrismaClient } from '@prisma/client'

export default fp(async (fastify) => {
  const prisma = new PrismaClient()
  await prisma.$connect()
  fastify.decorate('db', prisma)
  fastify.addHook('onClose', async () => await prisma.$disconnect())
})

declare module 'fastify' {
  interface FastifyInstance { db: PrismaClient }
}
Enter fullscreen mode Exit fullscreen mode
// src/routes/posts.ts
import { FastifyPluginAsync } from 'fastify'
import { Type } from '@sinclair/typebox'

const postsRouter: FastifyPluginAsync = async (fastify) => {
  fastify.get('/posts', async () => {
    return await fastify.db.post.findMany({ where: { published: true } })
  })

  fastify.post('/posts', {
    schema: {
      body: Type.Object({
        title: Type.String({ minLength: 1 }),
        content: Type.String()
      })
    }
  }, async (request, reply) => {
    const post = await fastify.db.post.create({ data: request.body })
    reply.status(201)
    return post
  })
}

export default postsRouter
Enter fullscreen mode Exit fullscreen mode
// src/server.ts
await server.register(databasePlugin)
await server.register(postsRouter, { prefix: '/api' })
Enter fullscreen mode Exit fullscreen mode

JWT Authentication

import jwt from '@fastify/jwt'

server.register(jwt, {
  secret: process.env.JWT_SECRET!,
  sign: { expiresIn: '7d' }
})

server.post('/auth/login', async (request, reply) => {
  const { email, password } = request.body as { email: string; password: string }
  const user = await db.user.findUnique({ where: { email } })

  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return reply.status(401).send({ error: 'Invalid credentials' })
  }

  const token = server.jwt.sign({ id: user.id, role: user.role })
  return { token }
})

server.decorate('authenticate', async (request, reply) => {
  try { await request.jwtVerify() }
  catch { reply.status(401).send({ error: 'Unauthorized' }) }
})

server.get('/api/profile', {
  preHandler: [server.authenticate]
}, async (request) => request.user)
Enter fullscreen mode Exit fullscreen mode

Testing with inject()

No real HTTP server needed for tests:

import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { buildServer } from './server'

const server = await buildServer()

afterAll(async () => await server.close())

describe('POST /users', () => {
  it('creates with valid data', async () => {
    const res = await server.inject({
      method: 'POST',
      url: '/users',
      payload: { name: 'Alice', email: 'alice@example.com' }
    })
    expect(res.statusCode).toBe(201)
    expect(res.json().id).toBeDefined()
  })

  it('returns 400 for invalid email', async () => {
    const res = await server.inject({
      method: 'POST',
      url: '/users',
      payload: { name: 'Bob', email: 'not-an-email' }
    })
    expect(res.statusCode).toBe(400)
  })
})
Enter fullscreen mode Exit fullscreen mode

Fastify vs Express vs Hono

Fastify Express Hono
Speed ~75k req/s ~35k req/s ~100k+ req/s
TypeScript First-class @types/express First-class
Validation Built-in (JSON Schema) External (zod/joi) zValidator middleware
Plugin system Encapsulated scope Global middleware Middleware stack
Runtime Node.js Node.js Multi-runtime
Best for High-throughput Node.js Legacy/simple apps Edge / Bun / multi-runtime

Rule of thumb: Staying on Node.js + need high throughput → Fastify. Multi-runtime or Bun → Hono.


Full article at stacknotice.com/blog/fastify-complete-guide-2026

Top comments (0)