Fastify Integration

Learn how to integrate Qryn with Fastify for high-performance API development.

Installation

Install Qryn and the Fastify adapter for seamless integration.

Install Dependencies

npm install qryn fastify
npm install -D @types/node

Basic Setup

import Fastify from 'fastify';
import { Qryn } from 'qryn';
import { createFastifyAdapter } from 'qryn/adapters/fastify';

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

const qryn = new Qryn({
  database: {
    type: 'sqlite',
    connection: './database.sqlite'
  }
});

// Create Fastify adapter
const qrynAdapter = createFastifyAdapter(qryn);

// Register Qryn plugin
fastify.register(qrynAdapter, { prefix: '/api/qryn' });

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

start();

Configuration

Configure Qryn with Fastify for optimal performance.

Fastify Configuration

Advanced Fastify integration options

Plugin Configuration

import Fastify from 'fastify';
import { Qryn } from 'qryn';
import { createFastifyAdapter } from 'qryn/adapters/fastify';

const fastify = Fastify({
  logger: {
    level: 'info',
    transport: {
      target: 'pino-pretty'
    }
  }
});

// Register CORS plugin
fastify.register(require('@fastify/cors'), {
  origin: true,
  credentials: true
});

// Register rate limit plugin
fastify.register(require('@fastify/rate-limit'), {
  max: 100,
  timeWindow: '1 minute'
});

const qryn = new Qryn({
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL
  }
});

// Register Qryn with custom options
fastify.register(createFastifyAdapter(qryn), {
  prefix: '/api/qryn',
  preHandler: async (request, reply) => {
    // Custom pre-handler
    const token = request.headers.authorization;
    if (!token) {
      reply.code(401).send({ error: 'Unauthorized' });
      return;
    }
  },
  preSerialization: async (request, reply, payload) => {
    // Custom serialization
    return payload;
  }
});

Error Handling

// Error handler
fastify.setErrorHandler((error, request, reply) => {
  fastify.log.error(error);
  
  if (error.validation) {
    reply.code(400).send({
      error: 'Validation Error',
      details: error.validation
    });
    return;
  }
  
  if (error.statusCode) {
    reply.code(error.statusCode).send({
      error: error.message
    });
    return;
  }
  
  reply.code(500).send({
    error: 'Internal Server Error'
  });
});

// Not found handler
fastify.setNotFoundHandler((request, reply) => {
  reply.code(404).send({
    error: 'Not Found',
    message: 'Route not found'
  });
});

Custom Routes

Add custom routes alongside Qryn's auto-generated API.

Custom Route Example

// Health check route
fastify.get('/health', async (request, reply) => {
  return { status: 'ok', timestamp: new Date().toISOString() };
});

// Stats route
fastify.get('/stats', async (request, reply) => {
  try {
    const stats = await qryn.query({
      model: 'User',
      select: ['id'],
      limit: 1
    });
    
    return {
      totalUsers: stats.meta.total,
      timestamp: new Date().toISOString()
    };
  } catch (error) {
    reply.code(500).send({ error: 'Failed to get stats' });
  }
});

// Authentication route
fastify.post('/auth/login', {
  schema: {
    body: {
      type: 'object',
      required: ['email', 'password'],
      properties: {
        email: { type: 'string', format: 'email' },
        password: { type: 'string', minLength: 6 }
      }
    }
  }
}, async (request, reply) => {
  try {
    const { email, password } = request.body;
    
    const user = await qryn.query({
      model: 'User',
      where: { email, password }
    });
    
    if (user.data.length === 0) {
      reply.code(401).send({ error: 'Invalid credentials' });
      return;
    }
    
    const token = generateJWT(user.data[0]);
    
    return { token, user: user.data[0] };
  } catch (error) {
    reply.code(500).send({ error: 'Login failed' });
  }
});

Route Hooks

// Global hooks
fastify.addHook('onRequest', async (request, reply) => {
  // Log all requests
  fastify.log.info({ method: request.method, url: request.url });
});

fastify.addHook('preHandler', async (request, reply) => {
  // Add request ID
  request.id = generateRequestId();
});

fastify.addHook('onResponse', async (request, reply) => {
  // Log response time
  const responseTime = reply.getResponseTime();
  fastify.log.info({ 
    requestId: request.id, 
    responseTime: `${responseTime}ms` 
  });
});

// Route-specific hooks
fastify.get('/api/protected', {
  preHandler: async (request, reply) => {
    const token = request.headers.authorization;
    if (!token) {
      reply.code(401).send({ error: 'Unauthorized' });
      return;
    }
  }
}, async (request, reply) => {
  return { message: 'Protected resource' };
});

Authentication

Implement authentication with Fastify and Qryn.

JWT Authentication

import jwt from 'jsonwebtoken';

// Register JWT plugin
fastify.register(require('@fastify/jwt'), {
  secret: process.env.JWT_SECRET
});

// Authentication decorator
fastify.decorate('authenticate', async function(request, reply) {
  try {
    await request.jwtVerify();
  } catch (err) {
    reply.send(err);
  }
});

// Login route
fastify.post('/auth/login', {
  schema: {
    body: {
      type: 'object',
      required: ['email', 'password'],
      properties: {
        email: { type: 'string', format: 'email' },
        password: { type: 'string' }
      }
    }
  }
}, async (request, reply) => {
  try {
    const { email, password } = request.body;
    
    const user = await qryn.query({
      model: 'User',
      where: { email, password }
    });
    
    if (user.data.length === 0) {
      reply.code(401).send({ error: 'Invalid credentials' });
      return;
    }
    
    const token = fastify.jwt.sign({ 
      userId: user.data[0].id,
      role: user.data[0].role 
    });
    
    return { token, user: user.data[0] };
  } catch (error) {
    reply.code(500).send({ error: 'Login failed' });
  }
});

// Protected route
fastify.get('/api/protected', {
  preHandler: [fastify.authenticate]
}, async (request, reply) => {
  return { 
    message: 'Protected resource',
    user: request.user 
  };
});

Role-based Authorization

// Authorization decorator
fastify.decorate('authorize', function(roles) {
  return async function(request, reply) {
    if (!request.user) {
      reply.code(401).send({ error: 'Authentication required' });
      return;
    }
    
    if (!roles.includes(request.user.role)) {
      reply.code(403).send({ error: 'Insufficient permissions' });
      return;
    }
  };
});

// Admin only route
fastify.get('/api/admin/users', {
  preHandler: [
    fastify.authenticate,
    fastify.authorize(['admin'])
  ]
}, async (request, reply) => {
  const users = await qryn.query({
    model: 'User',
    limit: 100
  });
  
  return users;
});

// User and admin route
fastify.get('/api/users/:id', {
  preHandler: [
    fastify.authenticate,
    fastify.authorize(['user', 'admin'])
  ]
}, async (request, reply) => {
  const { id } = request.params;
  
  // Users can only access their own data
  if (request.user.role === 'user' && request.user.userId !== id) {
    reply.code(403).send({ error: 'Access denied' });
    return;
  }
  
  const user = await qryn.query({
    model: 'User',
    where: { id }
  });
  
  return user.data[0];
});

Performance Optimization

Optimize Fastify with Qryn for maximum performance.

Performance Configuration

import Fastify from 'fastify';

const fastify = Fastify({
  logger: {
    level: 'info'
  },
  // Performance optimizations
  maxParamLength: 200,
  bodyLimit: 1048576, // 1MB
  keepAliveTimeout: 5000,
  connectionTimeout: 10000,
  // Compression
  compression: {
    global: true,
    threshold: 1024,
    encodings: ['gzip', 'deflate']
  }
});

// Register performance plugins
fastify.register(require('@fastify/compress'));
fastify.register(require('@fastify/helmet'));
fastify.register(require('@fastify/rate-limit'), {
  max: 1000,
  timeWindow: '1 minute'
});

// Qryn with performance optimizations
const qryn = new Qryn({
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL,
    pool: {
      min: 5,
      max: 50,
      idleTimeoutMillis: 30000
    }
  },
  cache: {
    type: 'redis',
    connection: process.env.REDIS_URL
  },
  performance: {
    queryOptimization: {
      enabled: true,
      batchSize: 100
    }
  }
});

Caching Strategy

// Register caching plugin
fastify.register(require('@fastify/caching'), {
  privacy: 'private',
  expiresIn: 300 // 5 minutes
});

// Cached route
fastify.get('/api/cached-data', {
  cache: {
    expiresIn: 300,
    privacy: 'private'
  }
}, async (request, reply) => {
  const data = await qryn.query({
    model: 'User',
    where: { status: 'active' },
    limit: 100
  });
  
  return data;
});

// Cache invalidation
fastify.post('/api/users', async (request, reply) => {
  const user = await qryn.create({
    model: 'User',
    data: request.body
  });
  
  // Invalidate cache
  fastify.cache.delete('/api/cached-data');
  
  return user;
});

Production Setup

Configure Fastify with Qryn for production deployment.

Production Configuration

import Fastify from 'fastify';
import { Qryn } from 'qryn';
import { createFastifyAdapter } from 'qryn/adapters/fastify';

const fastify = Fastify({
  logger: {
    level: process.env.LOG_LEVEL || 'info',
    transport: process.env.NODE_ENV === 'development' ? {
      target: 'pino-pretty'
    } : undefined
  },
  trustProxy: true,
  maxParamLength: 200,
  bodyLimit: 1048576
});

// Register security plugins
fastify.register(require('@fastify/helmet'), {
  contentSecurityPolicy: false
});

fastify.register(require('@fastify/cors'), {
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true
});

fastify.register(require('@fastify/rate-limit'), {
  max: 1000,
  timeWindow: '1 minute',
  redis: process.env.REDIS_URL
});

// Qryn configuration for production
const qryn = new Qryn({
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL,
    pool: {
      min: 5,
      max: 50,
      idleTimeoutMillis: 30000
    }
  },
  cache: {
    type: 'redis',
    connection: process.env.REDIS_URL
  },
  security: {
    rateLimit: {
      enabled: true,
      windowMs: 900000,
      max: 1000
    }
  }
});

// Register Qryn
fastify.register(createFastifyAdapter(qryn), {
  prefix: '/api/qryn'
});

// Health check
fastify.get('/health', async (request, reply) => {
  return { 
    status: 'ok', 
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  };
});

const start = async () => {
  try {
    const port = process.env.PORT || 3000;
    const host = process.env.HOST || '0.0.0.0';
    
    await fastify.listen({ port, host });
    console.log(`Server running on http://${host}:${port}`);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

Docker Configuration

# Dockerfile
FROM node:18-alpine

WORKDIR /app

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

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

Best Practices

Do

  • • Use Fastify plugins for common functionality
  • • Implement proper error handling
  • • Use schema validation
  • • Enable compression and security
  • • Implement rate limiting
  • • Use connection pooling
  • • Monitor performance

Don't

  • • Skip schema validation
  • • Ignore error handling
  • • Skip security middleware
  • • Ignore rate limiting
  • • Use inefficient queries
  • • Skip monitoring
  • • Ignore performance optimization