You can use Lightfast agents with Fastify, a fast and low overhead web framework for Node.js.
Installation
Install Fastify and required dependencies:
npm install fastify @fastify/cors @fastify/rate-limit @fastify/jwt
# or
yarn add fastify @fastify/cors @fastify/rate-limit @fastify/jwt
# or
pnpm add fastify @fastify/cors @fastify/rate-limit @fastify/jwt
Examples
The examples start a Fastify server that listens on port 8080. You can test it using curl
:
curl -X POST http://localhost:8080/agents/my-session \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"What'\''s the weather like?"}]}'
The examples use the OpenAI gpt-4o
model. Ensure that the OpenAI API key is set in the OPENAI_API_KEY
environment variable.
Full example: Available in our examples repository
Setup
First, create your agent and memory configuration:
import { createAgent } from 'lightfast/agent';
import { createTool } from 'lightfast/tool';
import { RedisMemory } from 'lightfast/memory/adapters/redis';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Create a simple tool
const weatherTool = createTool({
name: 'get_weather',
description: 'Get weather for a location',
parameters: z.object({
location: z.string().describe('The location to get weather for')
}),
execute: async ({ location }) => {
return `Weather in ${location}: Sunny, 72°F`;
}
});
// Create the agent
const agent = createAgent({
name: 'weather-assistant',
model: openai('gpt-4o'),
system: 'You are a helpful weather assistant.',
tools: { weather: weatherTool },
createRuntimeContext: ({ sessionId, resourceId }) => ({
timestamp: Date.now(),
framework: 'fastify'
})
});
// Create memory adapter
const memory = new RedisMemory({
url: process.env.REDIS_URL!,
token: process.env.REDIS_TOKEN!
});
Basic Integration
import Fastify from 'fastify';
import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
const fastify = Fastify({
logger: true,
bodyLimit: 10 * 1024 * 1024 // 10MB
});
fastify.post('/agents/:sessionId', async (request, reply) => {
// Convert Fastify request to Web API Request
const webRequest = new Request(`${request.protocol}://${request.hostname}${request.url}`, {
method: request.method,
headers: request.headers as any,
body: request.method === 'POST' ? JSON.stringify(request.body) : undefined
});
try {
const response = await fetchRequestHandler({
agent,
sessionId: request.params.sessionId,
memory,
req: webRequest,
resourceId: request.user?.id || 'anonymous',
createRequestContext: (req) => ({
userAgent: req.headers.get('user-agent'),
fastify: true,
version: fastify.version
})
});
// Convert Web API Response back to Fastify response
reply.status(response.status);
for (const [key, value] of response.headers) {
reply.header(key, value);
}
if (response.body) {
// Stream the response body
const reader = response.body.getReader();
const stream = new ReadableStream({
start(controller) {
const pump = async () => {
try {
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
controller.enqueue(value);
pump();
} catch (error) {
controller.error(error);
}
};
pump();
}
});
return reply.send(stream);
} else {
return reply.send();
}
} catch (error) {
fastify.log.error(error);
return reply.status(500).send({ error: 'Internal Server Error' });
}
});
const start = async () => {
try {
await fastify.listen({ port: 8080 });
console.log('Fastify server running on http://localhost:8080');
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
Advanced Integration with Plugins
import Fastify from 'fastify';
import cors from '@fastify/cors';
import rateLimit from '@fastify/rate-limit';
import jwt from '@fastify/jwt';
import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
// Create Fastify instance with TypeScript support
const fastify = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
prettyPrint: process.env.NODE_ENV === 'development'
}
});
// Register CORS
await fastify.register(cors, {
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'), false);
}
},
credentials: true
});
// Register rate limiting
await fastify.register(rateLimit, {
max: 100,
timeWindow: '1 minute',
keyGenerator: (request) => {
return request.ip;
}
});
// Register JWT
await fastify.register(jwt, {
secret: process.env.JWT_SECRET || 'supersecret'
});
// TypeScript declarations for Fastify
declare module 'fastify' {
interface FastifyRequest {
user?: {
id: string;
email: string;
role: string;
};
}
}
// Authentication hook
fastify.addHook('preHandler', async (request, reply) => {
// Skip authentication for health check
if (request.url === '/health') {
return;
}
try {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
return reply.status(401).send({ error: 'Missing authorization token' });
}
const decoded = fastify.jwt.verify(token) as any;
request.user = {
id: decoded.sub,
email: decoded.email,
role: decoded.role || 'user'
};
} catch (error) {
return reply.status(401).send({ error: 'Invalid token' });
}
});
// Agent chat endpoint
fastify.post<{
Params: { sessionId: string };
Body: { messages: Array<{ role: string; content: string }> };
}>('/agents/:sessionId', async (request, reply) => {
const { sessionId } = request.params;
const user = request.user!;
// Convert to Web API Request
const webRequest = new Request(`${request.protocol}://${request.hostname}${request.url}`, {
method: request.method,
headers: request.headers as any,
body: JSON.stringify(request.body)
});
try {
const response = await fetchRequestHandler({
agent,
sessionId,
memory,
req: webRequest,
resourceId: user.id,
createRequestContext: (req) => ({
userAgent: req.headers.get('user-agent'),
userRole: user.role,
fastify: true,
requestId: request.id
}),
onError: ({ error }) => {
fastify.log.error({ sessionId, userId: user.id, error }, 'Agent error');
}
});
// Handle streaming response
reply.status(response.status);
for (const [key, value] of response.headers) {
reply.header(key, value);
}
if (response.body) {
const reader = response.body.getReader();
const stream = new ReadableStream({
start(controller) {
const pump = async () => {
try {
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
controller.enqueue(value);
pump();
} catch (streamError) {
fastify.log.error({ sessionId, error: streamError }, 'Streaming error');
controller.error(streamError);
}
};
pump();
}
});
return reply.send(stream);
} else {
return reply.send();
}
} catch (error) {
fastify.log.error({ sessionId, userId: user.id, error }, 'Request processing error');
return reply.status(500).send({
error: 'Internal Server Error',
requestId: request.id
});
}
});
// Session management endpoints
fastify.get<{
Params: { sessionId: string };
}>('/agents/:sessionId/history', async (request, reply) => {
const { sessionId } = request.params;
const user = request.user!;
try {
// Verify session ownership
const session = await memory.getSession(sessionId);
if (!session || session.resourceId !== user.id) {
return reply.status(404).send({ error: 'Session not found' });
}
const messages = await memory.getMessages(sessionId);
return reply.send({ messages });
} catch (error) {
fastify.log.error({ sessionId, userId: user.id, error }, 'History retrieval error');
return reply.status(500).send({ error: 'Failed to retrieve history' });
}
});
fastify.delete<{
Params: { sessionId: string };
}>('/agents/:sessionId', async (request, reply) => {
const { sessionId } = request.params;
const user = request.user!;
try {
// Verify session ownership
const session = await memory.getSession(sessionId);
if (!session || session.resourceId !== user.id) {
return reply.status(404).send({ error: 'Session not found' });
}
await memory.deleteSession(sessionId);
return reply.status(204).send();
} catch (error) {
fastify.log.error({ sessionId, userId: user.id, error }, 'Session deletion error');
return reply.status(500).send({ error: 'Failed to delete session' });
}
});
// Health check endpoint
fastify.get('/health', async (request, reply) => {
return reply.send({
status: 'ok',
timestamp: Date.now(),
version: process.env.npm_package_version || '1.0.0',
uptime: process.uptime()
});
});
// Error handler
fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error);
// Don't leak error details in production
const message = process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: error.message;
reply.status(500).send({
error: message,
requestId: request.id
});
});
// Graceful shutdown
const gracefulShutdown = async () => {
fastify.log.info('Received shutdown signal, closing server...');
try {
await fastify.close();
fastify.log.info('Server closed successfully');
process.exit(0);
} catch (error) {
fastify.log.error(error, 'Error during shutdown');
process.exit(1);
}
};
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
const start = async () => {
try {
const port = process.env.PORT ? parseInt(process.env.PORT) : 8080;
const host = process.env.HOST || '0.0.0.0';
await fastify.listen({ port, host });
fastify.log.info(`Server listening on ${host}:${port}`);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
Plugin-based Architecture
Create reusable plugins for common functionality:
// plugins/lightfast.ts
import fp from 'fastify-plugin';
import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
import type { FastifyPluginAsync } from 'fastify';
interface LightfastOptions {
agent: any;
memory: any;
prefix?: string;
}
const lightfastPlugin: FastifyPluginAsync<LightfastOptions> = async (fastify, options) => {
const { agent, memory, prefix = '/agents' } = options;
// Chat endpoint
fastify.post(`${prefix}/:sessionId`, async (request, reply) => {
const webRequest = new Request(`${request.protocol}://${request.hostname}${request.url}`, {
method: request.method,
headers: request.headers as any,
body: JSON.stringify(request.body)
});
try {
const response = await fetchRequestHandler({
agent,
sessionId: request.params.sessionId,
memory,
req: webRequest,
resourceId: request.user?.id || 'anonymous',
createRequestContext: (req) => ({
userAgent: req.headers.get('user-agent'),
fastify: true,
plugin: true
})
});
reply.status(response.status);
for (const [key, value] of response.headers) {
reply.header(key, value);
}
if (response.body) {
const reader = response.body.getReader();
const stream = new ReadableStream({
start(controller) {
const pump = async () => {
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
controller.enqueue(value);
pump();
};
pump();
}
});
return reply.send(stream);
} else {
return reply.send();
}
} catch (error) {
fastify.log.error(error);
return reply.status(500).send({ error: 'Internal Server Error' });
}
});
// Session history
fastify.get(`${prefix}/:sessionId/history`, async (request, reply) => {
try {
const messages = await memory.getMessages(request.params.sessionId);
return reply.send({ messages });
} catch (error) {
fastify.log.error(error);
return reply.status(500).send({ error: 'Failed to retrieve history' });
}
});
};
export default fp(lightfastPlugin, {
fastify: '4.x',
name: 'lightfast'
});
// server.ts
import Fastify from 'fastify';
import lightfastPlugin from './plugins/lightfast';
const fastify = Fastify({ logger: true });
// Register the Lightfast plugin
await fastify.register(lightfastPlugin, {
agent,
memory,
prefix: '/api/agents'
});
await fastify.listen({ port: 8080 });
Testing
// test/agents.test.ts
import { build } from '../src/app';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('Agents API', () => {
let app: any;
beforeEach(async () => {
app = build({ logger: false });
await app.ready();
});
afterEach(async () => {
await app.close();
});
it('should handle chat requests', async () => {
const response = await app.inject({
method: 'POST',
url: '/agents/test-session',
headers: {
'content-type': 'application/json',
'authorization': 'Bearer valid-token'
},
payload: {
messages: [
{ role: 'user', content: 'Hello' }
]
}
});
expect(response.statusCode).toBe(200);
expect(response.headers['content-type']).toMatch(/text\/plain/);
});
it('should require authentication', async () => {
const response = await app.inject({
method: 'POST',
url: '/agents/test-session',
headers: {
'content-type': 'application/json'
},
payload: {
messages: [
{ role: 'user', content: 'Hello' }
]
}
});
expect(response.statusCode).toBe(401);
});
});
Performance Optimizations
Response Compression
import compress from '@fastify/compress';
await fastify.register(compress, {
threshold: 1024,
encodings: ['gzip', 'deflate']
});
Request Validation
const chatSchema = {
body: {
type: 'object',
required: ['messages'],
properties: {
messages: {
type: 'array',
items: {
type: 'object',
required: ['role', 'content'],
properties: {
role: { type: 'string', enum: ['user', 'assistant'] },
content: { type: 'string', minLength: 1 }
}
}
}
}
},
params: {
type: 'object',
required: ['sessionId'],
properties: {
sessionId: { type: 'string', minLength: 1 }
}
}
};
fastify.post('/agents/:sessionId', { schema: chatSchema }, async (request, reply) => {
// Request is automatically validated
// ...
});
Connection Pooling
// config/database.ts
export const redisConfig = {
url: process.env.REDIS_URL!,
token: process.env.REDIS_TOKEN!,
pool: {
min: 5,
max: 20,
acquireTimeoutMillis: 30000,
idleTimeoutMillis: 30000
}
};
Deployment
Docker
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 8080
USER node
CMD ["node", "dist/server.js"]
PM2
// ecosystem.config.js
module.exports = {
apps: [{
name: 'lightfast-api',
script: 'dist/server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 8080
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true
}]
};
Advantages
- Performance: One of the fastest Node.js frameworks
- TypeScript: Excellent TypeScript support with schema validation
- Plugin System: Rich ecosystem of plugins
- Logging: Built-in structured logging
- Validation: JSON Schema validation out of the box
Disadvantages
- Manual Streaming: Requires manual conversion for Web API streaming
- Learning Curve: Different from Express patterns
- Plugin Dependencies: Some features require additional plugins
Best Practices
- Use TypeScript for better type safety
- Validate inputs with JSON schemas
- Implement proper logging with structured logs
- Use plugins for common functionality
- Handle errors gracefully with proper error handlers
- Monitor performance with built-in metrics