Fastify Samples

Fastify high-performance Node.js web framework examples including plugins, hooks, validation, and optimization techniques

💻 Fastify Hello World javascript

🟢 simple

Basic Fastify server setup with routing, middleware, and essential features for high-performance web applications

// Fastify Hello World Examples

// 1. Basic Fastify Server
const fastify = require('fastify')({ logger: true });

// Declare a route
fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});

// Run the server
const start = async () => {
  try {
    await fastify.listen(3000);
    console.log('Server listening on http://localhost:3000');
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

// 2. Fastify with TypeScript
// src/server.ts
import fastify from 'fastify';
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';

const server: FastifyInstance = fastify({
  logger: {
    level: 'info',
    prettyPrint: true,
  },
});

// Define routes
server.get('/', async (request: FastifyRequest, reply: FastifyReply) => {
  return {
    message: 'Hello Fastify with TypeScript!',
    timestamp: new Date().toISOString(),
    version: '1.0.0'
  };
});

server.get('/health', async (request: FastifyRequest, reply: FastifyReply) => {
  return {
    status: 'ok',
    uptime: process.uptime(),
    memory: process.memoryUsage()
  };
});

// Start server
const start = async () => {
  try {
    const port = parseInt(process.env.PORT || '3000');
    const host = process.env.HOST || '0.0.0.0';

    await server.listen({ port, host });
    console.log(`🚀 Server ready at http://${host}:${port}`);
  } catch (err) {
    server.log.error(err);
    process.exit(1);
  }
};

start();

// 3. Fastify with Route Parameters and Validation
const fastify = require('fastify')({ logger: true });

// User data store (in production, use a database)
const users = [
  { id: 1, name: 'John Doe', email: '[email protected]', age: 30 },
  { id: 2, name: 'Jane Smith', email: '[email protected]', age: 25 },
  { id: 3, name: 'Bob Johnson', email: '[email protected]', age: 35 }
];

// Define schemas for validation
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'number' },
    name: { type: 'string', minLength: 1 },
    email: { type: 'string', format: 'email' },
    age: { type: 'number', minimum: 0, maximum: 150 }
  },
  required: ['name', 'email']
};

const createUserSchema = {
  type: 'object',
  properties: {
    name: { type: 'string', minLength: 1 },
    email: { type: 'string', format: 'email' },
    age: { type: 'number', minimum: 0, maximum: 150 }
  },
  required: ['name', 'email']
};

// Routes with validation
fastify.get('/api/users', {
  schema: {
    description: 'Get all users',
    tags: ['users'],
    response: {
      200: {
        type: 'array',
        items: userSchema
      }
    }
  }
}, async (request, reply) => {
  return { users, total: users.length };
});

fastify.get('/api/users/:id', {
  schema: {
    description: 'Get user by ID',
    tags: ['users'],
    params: {
      type: 'object',
      properties: {
        id: { type: 'number' }
      }
    },
    response: {
      200: userSchema,
      404: {
        type: 'object',
        properties: {
          error: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  const { id } = request.params;
  const user = users.find(u => u.id === id);

  if (!user) {
    reply.code(404).send({ error: 'User not found' });
    return;
  }

  return user;
});

fastify.post('/api/users', {
  schema: {
    description: 'Create a new user',
    tags: ['users'],
    body: createUserSchema,
    response: {
      201: userSchema,
      400: {
        type: 'object',
        properties: {
          error: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  const newUser = {
    id: users.length + 1,
    ...request.body
  };

  users.push(newUser);
  reply.code(201).send(newUser);
});

// 4. Fastify with Hooks and Middleware
const fastify = require('fastify')({ logger: true });

// Global hooks
fastify.addHook('onRequest', async (request, reply) => {
  const start = Date.now();
  request.startTime = start;
});

fastify.addHook('onResponse', async (request, reply) => {
  const duration = Date.now() - request.startTime;
  fastify.log.info(`Request to ${request.url} took ${duration}ms`);
});

// Authentication hook
fastify.addHook('preHandler', async (request, reply) => {
  // Skip auth for certain routes
  if (request.url.startsWith('/public') || request.url === '/') {
    return;
  }

  const authHeader = request.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    reply.code(401).send({ error: 'Unauthorized' });
    return;
  }

  // In production, verify JWT token here
  const token = authHeader.substring(7);
  if (token !== 'valid-token') {
    reply.code(401).send({ error: 'Invalid token' });
    return;
  }

  // Add user info to request
  request.user = { id: 1, name: 'Authenticated User' };
});

// Public routes
fastify.get('/public/info', async (request, reply) => {
  return { message: 'This is public information', timestamp: new Date().toISOString() };
});

// Protected routes
fastify.get('/protected/data', async (request, reply) => {
  return {
    message: 'This is protected data',
    user: request.user,
    timestamp: new Date().toISOString()
  };
});

// 5. Fastify with Plugins
// plugins/auth.js
const fp = require('fastify-plugin');

async function authPlugin(fastify, options) {
  fastify.decorate('authenticate', async (request, reply) => {
    try {
      await request.jwtVerify();
    } catch (err) {
      reply.send(err);
    }
  });
}

module.exports = fp(authPlugin);

// plugins/database.js
const fp = require('fastify-plugin');

async function databasePlugin(fastify, options) {
  // Mock database connection
  const db = {
    users: [],
    posts: [],

    async getUser(id) {
      return this.users.find(user => user.id === id);
    },

    async createUser(userData) {
      const user = { id: this.users.length + 1, ...userData };
      this.users.push(user);
      return user;
    }
  };

  fastify.decorate('db', db);
}

module.exports = fp(databasePlugin);

// Main server with plugins
const fastify = require('fastify')({ logger: true });

// Register plugins
fastify.register(require('fastify-jwt'), { secret: 'supersecret' });
fastify.register(require('./plugins/auth'));
fastify.register(require('./plugins/database'));

// Routes using plugins
fastify.get('/api/profile', {
  preHandler: [fastify.authenticate]
}, async (request, reply) => {
  const user = await fastify.db.getUser(request.user.id);
  return { user };
});

fastify.post('/api/users', async (request, reply) => {
  const user = await fastify.db.createUser(request.body);
  return user;
});

// 6. Fastify with File Uploads
const fastify = require('fastify')({ logger: true });
const path = require('path');
const fs = require('fs').promises;

// Register multipart plugin for file uploads
fastify.register(require('fastify-multipart'));

// Ensure upload directory exists
const uploadDir = path.join(__dirname, 'uploads');
fs.mkdir(uploadDir, { recursive: true }).catch(console.error);

// File upload route
fastify.post('/upload', async (request, reply) => {
  try {
    const data = await request.file();

    if (!data) {
      reply.code(400).send({ error: 'No file uploaded' });
      return;
    }

    // Generate unique filename
    const filename = `${Date.now()}-${data.filename}`;
    const filepath = path.join(uploadDir, filename);

    // Save file
    await pump(data.file, fs.createWriteStream(filepath));

    reply.send({
      message: 'File uploaded successfully',
      filename,
      size: data.file.bytesRead
    });
  } catch (error) {
    fastify.log.error(error);
    reply.code(500).send({ error: 'File upload failed' });
  }
});

// Multiple files upload
fastify.post('/upload/multiple', async (request, reply) => {
  try {
    const files = [];
    const parts = request.files();

    for await (const part of parts) {
      const filename = `${Date.now()}-${part.filename}`;
      const filepath = path.join(uploadDir, filename);

      await pump(part.file, fs.createWriteStream(filepath));

      files.push({
        filename,
        originalName: part.filename,
        size: part.file.bytesRead
      });
    }

    reply.send({
      message: 'Files uploaded successfully',
      files,
      count: files.length
    });
  } catch (error) {
    fastify.log.error(error);
    reply.code(500).send({ error: 'Multiple file upload failed' });
  }
});

// 7. Fastify with Error Handling
const fastify = require('fastify')({ logger: true });

// Custom error class
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

// Set error handler
fastify.setErrorHandler(function (error, request, reply) {
  // Log error
  this.log.error(error);

  // Send error response
  if (error.validation) {
    // Validation error
    reply.code(400).send({
      error: 'Validation Error',
      details: error.validation,
      statusCode: 400
    });
    return;
  }

  if (error.isOperational) {
    // Operational error (expected)
    reply.code(error.statusCode || 500).send({
      error: error.message,
      statusCode: error.statusCode || 500
    });
  } else {
    // Programming error
    reply.code(500).send({
      error: 'Internal Server Error',
      statusCode: 500
    });
  }
});

// Route that throws different errors
fastify.get('/error/validation', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        name: { type: 'string', minLength: 3 }
      },
      required: ['name']
    }
  }
}, async (request, reply) => {
  // This will cause validation error if name is too short
  return { message: `Hello ${request.query.name}` };
});

fastify.get('/error/operational', async (request, reply) => {
  throw new AppError('This is an operational error', 400);
});

fastify.get('/error/programming', async (request, reply) => {
  // This will be caught as a programming error
  throw new Error('Unexpected error occurred');
});

// 8. Fastify with Environment Configuration
const fastify = require('fastify')({
  logger: {
    level: process.env.LOG_LEVEL || 'info'
  }
});

// Configuration
const config = {
  port: parseInt(process.env.PORT || 3000),
  host: process.env.HOST || '0.0.0.0',
  database: {
    url: process.env.DATABASE_URL || 'mongodb://localhost:27017/fastify',
    name: process.env.DATABASE_NAME || 'fastify_db'
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'fallback-secret-key'
  },
  cors: {
    origin: process.env.CORS_ORIGIN || '*',
    credentials: true
  }
};

// Register environment-specific plugins
if (process.env.NODE_ENV === 'development') {
  fastify.register(require('fastify-cors'), config.cors);
}

// Health check endpoint
fastify.get('/health', async (request, reply) => {
  return {
    status: 'ok',
    environment: process.env.NODE_ENV || 'development',
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    config: {
      port: config.port,
      databaseName: config.database.name
    }
  };
});

// 9. Fastify with Testing Setup
// test/server.test.js
const { test } = require('tap');
const build = require('../server');

test('GET / route', async t => {
  const app = build();

  const response = await app.inject({
    method: 'GET',
    url: '/'
  });

  t.equal(response.statusCode, 200);
  t.same(response.json(), { hello: 'world' });
});

test('POST /api/users with valid data', async t => {
  const app = build();

  const userData = {
    name: 'Test User',
    email: '[email protected]',
    age: 25
  };

  const response = await app.inject({
    method: 'POST',
    url: '/api/users',
    payload: userData
  });

  t.equal(response.statusCode, 201);
  t.match(response.json(), userData);
});

test('POST /api/users with invalid data', async t => {
  const app = build();

  const invalidData = {
    name: '',  // Invalid: empty string
    email: 'invalid-email'  // Invalid: not a valid email
  };

  const response = await app.inject({
    method: 'POST',
    url: '/api/users',
    payload: invalidData
  });

  t.equal(response.statusCode, 400);
  t.ok(response.json().validation);
});

// 10. Fastify Production Best Practices
const fastify = require('fastify')({
  logger: {
    level: process.env.LOG_LEVEL || 'info',
    stream: process.stdout
  },
  // Enable trust proxy for proper IP logging behind reverse proxy
  trustProxy: true
});

// Graceful shutdown
process.on('SIGINT', async () => {
  fastify.log.info('Received SIGINT, shutting down gracefully...');
  await fastify.close();
  process.exit(0);
});

process.on('SIGTERM', async () => {
  fastify.log.info('Received SIGTERM, shutting down gracefully...');
  await fastify.close();
  process.exit(0);
});

// Health check for load balancers
fastify.get('/health', {
  schema: {
    hide: true  // Hide from documentation
  }
}, async (request, reply) => {
  return { status: 'ok' };
});

// Production-ready startup
const start = async () => {
  try {
    const port = parseInt(process.env.PORT || 3000);

    await fastify.listen({
      port,
      host: '0.0.0.0'  // Listen on all interfaces
    });

    fastify.log.info(`🚀 Server ready on port ${port}`);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();