LoginSign up
GitHub

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