LoginSign up
GitHub

You can use Lightfast agents with a native Node.js HTTP server for maximum control and minimal dependencies.

Examples

The examples start a simple HTTP 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()
  })
});

// Create memory adapter
const memory = new RedisMemory({
  url: process.env.REDIS_URL!,
  token: process.env.REDIS_TOKEN!
});

Basic Server

import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
import { createServer } from 'http';

const server = createServer(async (req, res) => {
  if (req.method === 'POST' && req.url?.startsWith('/agents/')) {
    // Extract sessionId from URL
    const sessionId = req.url.split('/').pop() || 'default';
    
    // Convert Node.js request to Web API Request
    const body = await new Promise<string>((resolve) => {
      let data = '';
      req.on('data', chunk => data += chunk);
      req.on('end', () => resolve(data));
    });

    const webRequest = new Request(`http://${req.headers.host}${req.url}`, {
      method: req.method,
      headers: req.headers as any,
      body: body || undefined
    });

    try {
      const response = await fetchRequestHandler({
        agent,
        sessionId,
        memory,
        req: webRequest,
        resourceId: 'user-123', // Replace with actual user ID
        createRequestContext: (req) => ({
          userAgent: req.headers.get('user-agent'),
          ip: req.headers.get('x-forwarded-for')
        })
      });

      // Convert Web API Response back to Node.js response
      res.statusCode = response.status;
      for (const [key, value] of response.headers) {
        res.setHeader(key, value);
      }

      if (response.body) {
        const reader = response.body.getReader();
        const pump = async () => {
          const { done, value } = await reader.read();
          if (done) {
            res.end();
            return;
          }
          res.write(value);
          pump();
        };
        pump();
      } else {
        res.end();
      }
    } catch (error) {
      res.statusCode = 500;
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify({ error: 'Internal Server Error' }));
    }
  } else {
    res.statusCode = 404;
    res.end('Not Found');
  }
});

server.listen(8080, () => {
  console.log('Server running on http://localhost:8080');
});

Advanced Server with Routing

import { fetchRequestHandler } from 'lightfast/server/adapters/fetch';
import { createServer } from 'http';
import { parse } from 'url';

const server = createServer(async (req, res) => {
  const { pathname, query } = parse(req.url || '', true);
  
  // Enable CORS
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  
  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    res.end();
    return;
  }

  // Route: POST /agents/:sessionId
  const agentMatch = pathname?.match(/^\/agents\/(.+)$/);
  if (req.method === 'POST' && agentMatch) {
    const sessionId = agentMatch[1];
    
    try {
      // Parse request body
      const body = await new Promise<string>((resolve, reject) => {
        let data = '';
        req.on('data', chunk => data += chunk);
        req.on('end', () => resolve(data));
        req.on('error', reject);
      });

      // Create Web API Request
      const webRequest = new Request(`http://${req.headers.host}${req.url}`, {
        method: req.method,
        headers: req.headers as any,
        body: body || undefined
      });

      const response = await fetchRequestHandler({
        agent,
        sessionId,
        memory,
        req: webRequest,
        resourceId: extractUserId(req) || 'anonymous',
        createRequestContext: (req) => ({
          userAgent: req.headers.get('user-agent'),
          ip: req.headers.get('x-forwarded-for'),
          timestamp: Date.now()
        }),
        onError: ({ error }) => {
          console.error(`Agent error for session ${sessionId}:`, error);
        }
      });

      // Stream response
      res.statusCode = response.status;
      for (const [key, value] of response.headers) {
        res.setHeader(key, value);
      }

      if (response.body) {
        const reader = response.body.getReader();
        const pump = async () => {
          try {
            const { done, value } = await reader.read();
            if (done) {
              res.end();
              return;
            }
            res.write(value);
            pump();
          } catch (error) {
            console.error('Streaming error:', error);
            res.end();
          }
        };
        pump();
      } else {
        res.end();
      }
    } catch (error) {
      console.error('Request error:', error);
      res.statusCode = 500;
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify({ 
        error: 'Internal Server Error',
        message: error instanceof Error ? error.message : 'Unknown error'
      }));
    }
    return;
  }

  // Route: GET /health
  if (req.method === 'GET' && pathname === '/health') {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));
    return;
  }

  // 404 for unmatched routes
  res.statusCode = 404;
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify({ error: 'Not Found' }));
});

// Helper function to extract user ID from request
function extractUserId(req: any): string | null {
  // Extract from Authorization header, query param, etc.
  const authHeader = req.headers.authorization;
  if (authHeader?.startsWith('Bearer ')) {
    // Parse JWT or API key to get user ID
    return 'user-from-token';
  }
  return null;
}

server.listen(8080, () => {
  console.log('Advanced server running on http://localhost:8080');
  console.log('Endpoints:');
  console.log('  POST /agents/:sessionId - Chat with agent');
  console.log('  GET /health - Health check');
});

Production Considerations

Cluster Mode

For production use, consider running multiple processes:

import cluster from 'cluster';
import { cpus } from 'os';

if (cluster.isPrimary) {
  const numCPUs = cpus().length;
  console.log(`Primary ${process.pid} is running`);

  // Fork workers
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork(); // Replace the dead worker
  });
} else {
  // Worker process - run your server here
  createServer(/* your server logic */).listen(8080);
  console.log(`Worker ${process.pid} started`);
}

Error Handling

// Global error handlers
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1);
});

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('Received SIGINT, shutting down gracefully');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});

Environment Configuration

// config.ts
export const config = {
  port: process.env.PORT ? parseInt(process.env.PORT) : 8080,
  host: process.env.HOST || '0.0.0.0',
  redis: {
    url: process.env.REDIS_URL!,
    token: process.env.REDIS_TOKEN!,
  },
  openai: {
    apiKey: process.env.OPENAI_API_KEY!,
  }
};

// Validate required environment variables
const requiredEnvVars = ['REDIS_URL', 'REDIS_TOKEN', 'OPENAI_API_KEY'];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    console.error(`Missing required environment variable: ${envVar}`);
    process.exit(1);
  }
}

Testing

Test your server with different scenarios:

# Basic chat
curl -X POST http://localhost:8080/agents/test-session \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"Hello!"}]}'

# Weather query
curl -X POST http://localhost:8080/agents/weather-session \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"What'\''s the weather in Tokyo?"}]}'

# Health check
curl http://localhost:8080/health

Advantages

  • Full Control: Complete control over the HTTP server
  • Minimal Dependencies: No framework overhead
  • Performance: Direct access to Node.js HTTP primitives
  • Flexibility: Easy to customize routing and middleware

Disadvantages

  • More Code: Requires manual request/response handling
  • Security: Need to implement security features manually
  • Routing: No built-in routing system