Skip to main content
Fastify is a lightweight, high-performance web framework for Node.js, designed to deliver exceptional speed with minimal overhead. It provides a streamlined, developer-friendly API, powerful plugin architecture, and built-in schema validation for both requests and responses. With its focus on efficiency and extensibility, Fastify is well-suited for building scalable APIs and backend services that require strong performance without sacrificing maintainability. Fastify can only be used on Application Hosting; it cannot be deployed as a static site.

Configuration

With Fastify, you can either compile TypeScript to JavaScript for production or run TypeScript directly.
  • Sevalla will run npm run build followed by npm start
  • Provides faster startup and better runtime performance
Example package.json:
{
  "scripts": {
    "start": "node dist/index.js",
    "build": "tsc",
    "dev": "tsx watch src/index.ts"
  }
}

Run TypeScript directly

  • Sevalla will run npm start using tsx or ts-node
  • Simpler setup with no build step.
Example package.json:
{
  "scripts": {
    "start": "tsx src/index.ts",
    "dev": "tsx watch src/index.ts"
  }
}

Required for Sevalla deployments

Ensure your Fastify server listens on all interfaces (not just localhost):
await fastify.listen({ port: PORT, host: "0.0.0.0" });

Containerization

Dockerfile

The build for Dockerfiles is fully customizable. We recommend the following best practices when using a Dockerfile for Fastify on Sevalla:
  1. Use Alpine images - Smaller image size (~40MB vs ~900MB).
  2. Multi-stage builds - Separate build and runtime dependencies.
  3. Layer caching - Copy package.json before the source code.
  4. Security - Run as a non-root user in production.
  5. .dockerignore - Exclude unnecessary files.
The following is an example Dockerfile for Fastify:
# Build stage
FROM node:lts-alpine AS builder

WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci

COPY . .
RUN npm run build

# Production stage
FROM node:lts-alpine

WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci --only=production

COPY --from=builder /app/dist ./dist

EXPOSE 3000

ENV PORT=3000
ENV HOST=0.0.0.0
ENV NODE_ENV=production

CMD ["node", "dist/index.js"]

Nixpacks

Nixpacks automatically detects the lock file and uses the corresponding package manager during deployment. You can also modify the node version through the NIXPACKS_NODE_VERSION environment variable. We recommend the following best practices when using Nixpacks for Fastify on Sevalla:
  • Commit lock files - Always include package-lock.json, yarn.lock, or pnpm-lock.yaml for deterministic, reproducible builds.
  • Specify your Node.js version - Use the engines field in package.json or an .nvmrc file to ensure consistent versioning.
  • Prefer npm ci - More reliable and faster than npm install in CI/CD environments.
  • Set NODE_ENV=production - Helps optimize performance and reduce bundle size.
  • Test builds locally - Use the Nixpacks CLI to identify build issues before deploying.
  • Keep configuration minimal - Allow Nixpacks to auto-detect your setup; only add a nixpacks.toml file for advanced customization.
  • Optimize TypeScript builds - After compiling, run npm prune --production to remove dev dependencies.
  • Define a startscript - Ensure package.json includes a start script for consistent server startup.
Example nixpacks.toml file:
# nixpacks.toml
[phases.setup]
nixPkgs = ["nodejs_20"]

[start]
cmd = "node dist/index.js"

Buildpacks

Buildpacks, automatically detects your project’s lock file and installs dependencies using the appropriate package manager. When using Buildpacks, you cannot directly modify the underlying build phases or control how dependencies are installed; Buildpacks determine this based on your project’s structure and configuration. However, you can influence the final runtime behavior by defining the appropriate start script in your package.json file, which Buildpacks will use when launching your Fastify application.

CDN

Sevalla provides a premium, Cloudflare-powered CDN for Application Hosting at no additional cost. To get the most out of Sevalla’s CDN when deploying your Fastify application, we recommend the following best practices:
  • Enable the CDN for all production applications.
  • Set appropriate Cache-Control headers on API routes to ensure proper caching behavior.
  • Purge the CDN cache after deploying critical updates to avoid serving stale content.
  • Use versioned URLs for static assets, for example /static/app.v123.js .

Optimizing Fastify for CDN

Install the static files plugin

npm install @fastify/static

Static files with cache headers

// src/index.ts
import Fastify from "fastify";
import fastifyStatic from "@fastify/static";
import path from "path";

const fastify = Fastify({
  logger: true,
});

const PORT = Number(process.env.PORT) || 3000;

// Serve static files with caching
fastify.register(fastifyStatic, {
  root: path.join(__dirname, "../public"),
  prefix: "/static/",
  decorateReply: false,
  setHeaders: (res, filePath) => {
    res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
  },
});

// API endpoint with shorter cache
fastify.get("/api/data", async (request, reply) => {
  const data = { message: "Hello from Fastify!" };

  return reply
    .header("Cache-Control", "public, max-age=3600, s-maxage=3600")
    .send(data);
});

// 404 handler
fastify.setNotFoundHandler((request, reply) => {
  return reply.status(404).send("Not Found");
});

// Start server
const start = async () => {
  try {
    await fastify.listen({ port: PORT, host: "0.0.0.0" });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

Edge caching

Edge caching stores your Sevalla site cache on Cloudflare’s 260+ global data centers, delivering responses from the location nearest to each visitor for faster performance. To maximize the benefits of Sevalla’s Edge Caching for your Fastify application, we recommend the following best practices:
  • Set appropriate Cache-Control headers in loaders to control caching behavior.
  • Combine edge caching with the CDN for a complete caching strategy.

Optimizing Fastify for edge caching

// src/index.ts
import Fastify from "fastify";

const fastify = Fastify({
  logger: true,
});

const PORT = Number(process.env.PORT) || 3000;

// API with edge caching
fastify.get("/api/products", async (request, reply) => {
  const products = await getProducts(); // Your data fetching logic

  return reply
    .header("Cache-Control", "public, max-age=60, s-maxage=3600")
    .send(products);
});

// User-specific data (don't cache)
fastify.get("/api/user/profile", async (request, reply) => {
  const userData = { user: "data" };

  return reply.header("Cache-Control", "private, no-cache").send(userData);
});

// Start server
const start = async () => {
  try {
    await fastify.listen({ port: PORT, host: "0.0.0.0" });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

Cache-Control

With Sevalla’s Cloudflare integration,Cache-Controlheaders are respected at the edge, giving you precise control over how content is cached and served globally. The following are some common directives you can use in your Fastify application:
  • public, s-maxage=3600 - Caches the response on the CDN for 1 hour, improving performance for frequently accessed content.
  • public, max-age=31536000, immutable - Ideal for versioned static assets, allowing them to be cached for up to 1 year with no revalidation.
  • private - Prevents CDN caching and ensures the response is only cached by the end user’s browser, for personalized or sensitive data.
Sevalla does not yet support the stale-while-revalidate Cache-Control directive. To prevent unexpected caching behavior, we recommend not using this directive in your API or asset caching settings.

Health checks

Ensure your application remains available during deployments by implementing health checks:
  • Always implement health checks for production applications.
  • Keep checks lightweight; responses should complete in under 1 second.
  • Verify critical dependencies (e.g., databases, Redis) as part of the checks.
  • Return 200 for degraded states to allow deployments to continue smoothly.
  • Return 503 only for critical failures that require pod restarts.

Basic health check

// src/index.ts
import Fastify from "fastify";

const fastify = Fastify({ logger: true });
const PORT = Number(process.env.PORT) || 3000;

fastify.get("/api/health", async (request, reply) => {
  return reply.send({
    status: "ok",
    timestamp: new Date().toISOString(),
    runtime: "Node.js",
    version: process.version,
  });
});

const start = async () => {
  try {
    await fastify.listen({ port: PORT, host: "0.0.0.0" });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

Graceful shutdown

Fastify’s built-in close() method handles graceful shutdown automatically by:
  • Stopping the server from accepting new connections.
  • Waiting for existing requests to complete.
  • Closing all connections.
  • Cleaning up registered plugins and hooks.
Implement graceful shutdown to cleanly close connections during deployments. For example:
// src/index.ts
import Fastify from "fastify";
import { pool } from "./lib/db";

const fastify = Fastify({ logger: true });
const PORT = Number(process.env.PORT) || 3000;

// ... your routes

// Graceful shutdown function
const closeGracefully = async (signal: string) => {
  fastify.log.info(`Received ${signal}, closing gracefully...`);

  try {
    // Fastify.close() stops accepting new connections and closes server
    await fastify.close();
    fastify.log.info("Fastify server closed");

    // Close database pool
    await pool.end();
    fastify.log.info("Database pool closed");

    fastify.log.info("Server closed gracefully");
    process.exit(0);
  } catch (err) {
    fastify.log.error("Error during graceful shutdown:", err);
    process.exit(1);
  }
};

// Listen for termination signals
process.on("SIGTERM", () => closeGracefully("SIGTERM"));
process.on("SIGINT", () => closeGracefully("SIGINT"));

// Start server
const start = async () => {
  try {
    await fastify.listen({ port: PORT, host: "0.0.0.0" });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();