You can use Lightfast agents with Express.js to build robust web applications with AI capabilities.
Installation
Install Express and required dependencies:
npm install express @types/express
# or
yarn add express @types/express
# or
pnpm add express @types/express
Examples
The examples start an Express 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.
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(),
environment: 'express'
})
});
// Create memory adapter
const memory = new RedisMemory({
url: process.env.REDIS_URL!,
token: process.env.REDIS_TOKEN!
});
Basic Integration
import express from 'express';
import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Agent endpoint
app.post('/agents/:sessionId', async (req, res) => {
// Convert Express request to Web API Request
const webRequest = new Request(`${req.protocol}://${req.get('host')}${req.originalUrl}`, {
method: req.method,
headers: req.headers as any,
body: JSON.stringify(req.body)
});
try {
const response = await fetchRequestHandler({
agent,
sessionId: req.params.sessionId,
memory,
req: webRequest,
resourceId: req.user?.id || 'anonymous',
createRequestContext: (req) => ({
userAgent: req.headers.get('user-agent'),
ip: req.headers.get('x-forwarded-for'),
express: true
})
});
// Stream Web API Response back through Express
res.status(response.status);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
if (response.body) {
const reader = response.body.getReader();
const pump = async () => {
const { done, value } = await reader.read();
if (done) return res.end();
res.write(value);
pump();
};
pump();
} else {
res.end();
}
} catch (error) {
console.error('Agent error:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.listen(8080, () => {
console.log('Express server running on http://localhost:8080');
});
Advanced Integration with Middleware
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/agents', limiter);
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Custom middleware for authentication
interface AuthenticatedRequest extends express.Request {
user?: {
id: string;
email: string;
role: string;
};
}
const authenticateUser = async (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' });
}
const token = authHeader.slice(7);
// Validate token and extract user info
const user = await validateToken(token); // Implement your token validation
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Agent routes with authentication
app.post('/agents/:sessionId', authenticateUser, async (req: AuthenticatedRequest, res) => {
const webRequest = new Request(`${req.protocol}://${req.get('host')}${req.originalUrl}`, {
method: req.method,
headers: req.headers as any,
body: JSON.stringify(req.body)
});
try {
const response = await fetchRequestHandler({
agent,
sessionId: req.params.sessionId,
memory,
req: webRequest,
resourceId: req.user!.id,
createRequestContext: (req) => ({
userAgent: req.headers.get('user-agent'),
ip: req.headers.get('x-forwarded-for'),
userRole: req.user!.role,
express: true
}),
onError: ({ error }) => {
console.error(`Agent error for user ${req.user!.id}:`, error);
}
});
// Stream response
res.status(response.status);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
if (response.body) {
const reader = response.body.getReader();
const pump = async () => {
try {
const { done, value } = await reader.read();
if (done) return res.end();
res.write(value);
pump();
} catch (streamError) {
console.error('Streaming error:', streamError);
res.end();
}
};
pump();
} else {
res.end();
}
} catch (error) {
console.error('Request error:', error);
res.status(500).json({
error: 'Internal Server Error',
requestId: generateRequestId()
});
}
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: Date.now(),
version: process.env.npm_package_version || '1.0.0'
});
});
// Error handling middleware
app.use((error: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error('Express error:', error);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? error.message : undefined
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Not Found' });
});
// Helper functions
async function validateToken(token: string) {
// Implement your token validation logic
// This could use JWT, database lookup, etc.
return {
id: 'user-123',
email: 'user@example.com',
role: 'user'
};
}
function generateRequestId(): string {
return Math.random().toString(36).substring(2, 15);
}
const port = process.env.PORT ? parseInt(process.env.PORT) : 8080;
app.listen(port, () => {
console.log(`Express server running on http://localhost:${port}`);
});
Router-based Organization
For larger applications, organize routes using Express Router:
// routes/agents.ts
import { Router } from 'express';
import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
const router = Router();
// Chat with agent
router.post('/:sessionId', async (req, res) => {
const webRequest = new Request(`${req.protocol}://${req.get('host')}${req.originalUrl}`, {
method: req.method,
headers: req.headers as any,
body: JSON.stringify(req.body)
});
try {
const response = await fetchRequestHandler({
agent,
sessionId: req.params.sessionId,
memory,
req: webRequest,
resourceId: req.user?.id || 'anonymous',
createRequestContext: (req) => ({
userAgent: req.headers.get('user-agent'),
route: 'agents'
})
});
// Stream response
res.status(response.status);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
if (response.body) {
const reader = response.body.getReader();
const pump = async () => {
const { done, value } = await reader.read();
if (done) return res.end();
res.write(value);
pump();
};
pump();
} else {
res.end();
}
} catch (error) {
console.error('Agent chat error:', error);
res.status(500).json({ error: 'Failed to process chat request' });
}
});
// Get session history
router.get('/:sessionId/history', async (req, res) => {
try {
const messages = await memory.getMessages(req.params.sessionId);
res.json({ messages });
} catch (error) {
console.error('History retrieval error:', error);
res.status(500).json({ error: 'Failed to retrieve history' });
}
});
// Delete session
router.delete('/:sessionId', async (req, res) => {
try {
await memory.deleteSession(req.params.sessionId);
res.status(204).end();
} catch (error) {
console.error('Session deletion error:', error);
res.status(500).json({ error: 'Failed to delete session' });
}
});
export { router as agentsRouter };
// app.ts
import express from 'express';
import { agentsRouter } from './routes/agents';
const app = express();
app.use(express.json());
app.use('/agents', agentsRouter);
app.listen(8080);
TypeScript Integration
For better type safety with Express:
// types/express.d.ts
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
role: string;
};
requestId?: string;
}
}
}
export {};
// middleware/types.ts
import { Request, Response, NextFunction } from 'express';
export interface TypedRequest<T = any> extends Request {
body: T;
}
export interface ChatRequest {
messages: Array<{
role: 'user' | 'assistant';
content: string;
}>;
}
// Usage
app.post('/agents/:sessionId', (req: TypedRequest<ChatRequest>, res: Response) => {
// req.body is now typed as ChatRequest
const { messages } = req.body;
// ...
});
Testing
// test/agents.test.ts
import request from 'supertest';
import { app } from '../app';
describe('Agents API', () => {
it('should handle chat requests', async () => {
const response = await request(app)
.post('/agents/test-session')
.send({
messages: [
{ role: 'user', content: 'Hello' }
]
})
.expect(200);
expect(response.headers['content-type']).toMatch(/text\/plain/);
});
it('should require authentication', async () => {
await request(app)
.post('/agents/test-session')
.send({
messages: [
{ role: 'user', content: 'Hello' }
]
})
.expect(401);
});
});
Production Deployment
// server.ts
import express from 'express';
import cluster from 'cluster';
import { cpus } from 'os';
if (cluster.isPrimary && process.env.NODE_ENV === 'production') {
const numCPUs = cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
const app = express();
// Your app configuration
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}
Advantages
- Ecosystem: Huge ecosystem of middleware and plugins
- Flexibility: Highly configurable and extensible
- Community: Large community and extensive documentation
- Middleware: Rich middleware ecosystem for authentication, validation, etc.
Disadvantages
- Performance: Not the fastest Node.js framework
- Manual Streaming: Requires manual conversion for Web API streaming
- Overhead: Framework overhead compared to native Node.js