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/nodeBasic 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