Articles
AI Article 6 min 766

FastAPI vs Go Fiber vs Node Fastify: API Benchmark at 10K rps

Comparing three JSON API frameworks: FastAPI, Go Fiber, Fastify. PostgreSQL benchmarks, middleware, testing, Docker. When to choose which.

This article was generated by an AI model and may contain inaccuracies. Verify information before using in production.

Three Frameworks, One Task: Fast API

When you need to build an API service — not a monolith with ORM and templates, but a pure JSON API with 10-50 endpoints and minimal latency — the stack choice determines everything: from performance to infrastructure costs.

Three frameworks compete for this segment: FastAPI (Python) with auto-documentation and types, Go Fiber with C-level performance, Node Fastify with the npm ecosystem and async/await. Each promises speed. Each solves the problem differently.

I've worked with FastAPI on an ML pipeline (inference API), Go Fiber on an auth microservice (50K+ rps), Fastify on a BFF layer (aggregating 5 microservices). Here's what I took away from each.

Architecture and Philosophy

FastAPI — Types and Docs Out of the Box

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI(title="User API", version="1.0.0")

class UserCreate(BaseModel):
    name: str
    email: EmailStr
    age: int | None = None

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    db_user = await db.users.insert(user.model_dump())
    return UserResponse(**db_user)

Launch it — Swagger UI at /docs, ReDoc at /redoc. Pydantic types auto-generate JSON Schema.

Go Fiber — Minimalism and Speed

app := fiber.New(fiber.Config{Prefork: true})

app.Post("/users", func(c *fiber.Ctx) error {
    var input UserCreate
    if err := c.BodyParser(&input); err != nil {
        return c.Status(400).JSON(fiber.Map{"error": "Invalid JSON"})
    }
    if err := validate.Struct(input); err != nil {
        return c.Status(422).JSON(fiber.Map{"error": err.Error()})
    }
    user := db.CreateUser(input)
    return c.Status(201).JSON(user)
})

app.Listen(":3000")

No auto-documentation (need swaggo). No auto-validation (need validator). But: goroutines instead of event loop, compiled binary, minimal overhead.

Node Fastify — Structured Express

const app = Fastify({ logger: true });

app.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 2 },
        email: { type: 'string', format: 'email' },
      },
    },
  },
}, async (request, reply) => {
  const user = await db.users.create(request.body);
  reply.code(201).send(user);
});

Fastify uses JSON Schema for validation (Ajv) and serialization (fast-json-stringify). Schema describes both input and output — Fastify generates an optimized serializer.

Architecture Comparison

Aspect FastAPI Go Fiber Node Fastify
Language Python 3.10+ Go 1.21+ TypeScript/JS
Concurrency async/await (asyncio) Goroutines Event loop
Validation Pydantic (built-in) go-validator JSON Schema (Ajv)
Documentation Swagger UI (built-in) swaggo (third-party) @fastify/swagger
Ecosystem PyPI (500K+) Go modules npm (2M+)

Benchmarks

Tests: 4 cores, 16 GB RAM, PostgreSQL 16 with 1M rows, wrk with 256 connections, 30 seconds.

Static JSON Response (no DB)

Metric FastAPI Go Fiber Node Fastify
Requests/sec 18,500 185,000 62,000
Latency p50 12ms 1.2ms 3.5ms
Memory 85 MB 12 MB 65 MB

CRUD with PostgreSQL

Metric FastAPI Go Fiber Node Fastify
Requests/sec 4,200 12,800 8,500
Latency p50 55ms 18ms 28ms
Memory 120 MB 25 MB 95 MB

With a database the gap shrinks: Go Fiber is 3x faster than FastAPI, Fastify 2x. The bottleneck moves to PostgreSQL.

Heavy Request (aggregation + external API)

Metric FastAPI Go Fiber Node Fastify
Requests/sec 850 2,100 1,800
Latency p50 280ms 115ms 135ms

On I/O-bound tasks, Fastify approaches Go thanks to the event loop. FastAPI loses due to GIL and asyncio overhead.

Startup Time

Metric FastAPI Go Fiber Node Fastify
Cold start 1.2s 0.05s 0.8s
Docker image 150 MB 12 MB 80 MB

Go has instant startup. Critical for serverless where cold start = money.

Middleware and Auth

FastAPI: Dependency Injection

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Security(security),
) -> User:
    payload = jwt.decode(credentials.credentials, SECRET_KEY)
    user = await db.users.find_one(payload["user_id"])
    if not user:
        raise HTTPException(401, "Invalid token")
    return user

@app.get("/admin/users")
async def admin_users(admin: User = Depends(require_admin)):
    return await db.users.find_all()

Go Fiber: Middleware Chain

func AuthMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        token := c.Get("Authorization")
        claims, err := jwt.Parse(strings.TrimPrefix(token, "Bearer "))
        if err != nil {
            return c.Status(401).JSON(fiber.Map{"error": "Invalid token"})
        }
        c.Locals("user_id", claims.UserID)
        return c.Next()
    }
}

admin := app.Group("/admin", AuthMiddleware(), AdminOnly())

Node Fastify: Hooks

app.decorate('authenticate', async (request, reply) => {
  const token = request.headers.authorization?.replace('Bearer ', '');
  request.user = jwt.verify(token, process.env.JWT_SECRET!);
});

app.get('/admin/users', { preHandler: [app.authenticate] }, handler);

Testing

FastAPI

def test_create_product(client):
    response = client.post("/products", json={"name": "Test", "price": 29.99, "category": "test"})
    assert response.status_code == 201
    assert response.json()["name"] == "Test"

Go Fiber

req := httptest.NewRequest("POST", "/products", strings.NewReader(body))
resp, _ := app.Test(req, -1)
assert.Equal(t, 201, resp.StatusCode)

Node Fastify

const response = await app.inject({
  method: 'POST', url: '/products',
  payload: { name: 'Test', price: 29.99, category: 'test' },
});
assert.strictEqual(response.statusCode, 201);

When to Choose What

FastAPI

  • ML/AI inference API: PyTorch, TensorFlow, scikit-learn integration
  • Data-heavy API: pandas, numpy in the pipeline
  • Rapid prototyping: auto-documentation saves days
  • Team knows Python best
  • Load < 5K rps

Go Fiber

  • High-performance API: > 10K rps, low latency critical
  • Microservices: tiny Docker image, instant cold start
  • Serverless (Lambda): 50ms cold start vs 1200ms Python
  • Infrastructure services: proxy, gateway, auth
  • Predictable performance without GC pauses

Node Fastify

  • BFF (Backend-for-Frontend): aggregating multiple APIs
  • Real-time: WebSocket + HTTP in one process
  • Fullstack TypeScript: shared types between front and back
  • npm ecosystem is critical
  • Frontend team writing backend
  • Load 5-15K rps

None of the Three

  • Monolith with ORM, migrations, templates → Rails, Laravel, Django
  • Enterprise CQRS, DDD → Spring Boot, .NET
  • Systems programming → Rust (Actix, Axum)

Final Checklist

Choice

  • [ ] Primary load: CPU-bound → Go, I/O-bound → Fastify/FastAPI, ML → FastAPI
  • [ ] Target rps: < 5K → any, 5-15K → Fastify/Go, > 15K → Go
  • [ ] Ecosystem: PyPI (data/ML), npm (frontend), Go modules (infra)
  • [ ] Cold start: serverless → Go, containers → any

Architecture

  • [ ] Framework-level validation: Pydantic / JSON Schema / go-validator
  • [ ] Structured errors: uniform {"error": ..., "code": ...}
  • [ ] Health check: GET /health with dependency checks
  • [ ] Graceful shutdown: handle SIGTERM

Production

  • [ ] Rate limiting: built-in or nginx
  • [ ] CORS: configure for specific domains
  • [ ] Structured logging: JSON logs for collector
  • [ ] Metrics: Prometheus /metrics
  • [ ] Docker: multi-stage build, non-root user

Comments (0)