Skip to Content
Veltix
AdvancedAPI Configuration

API Configuration

This guide covers configuring and customizing the Veltix API for different use cases.

Overview

The Veltix API provides:

  • RESTful endpoints for data operations
  • WebSocket connections for real-time updates
  • Authentication and authorization
  • Rate limiting and security
  • Custom middleware support

API Server Setup

Basic Configuration

// apps/api/src/server.ts import fastify from 'fastify'; import cors from '@fastify/cors'; import rateLimit from '@fastify/rate-limit'; const server = fastify({ logger: true, trustProxy: true }); // CORS configuration await server.register(cors, { origin: ['http://localhost:3000', 'https://yourdomain.com'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] }); // Rate limiting await server.register(rateLimit, { max: 100, timeWindow: '1 minute', allowList: ['127.0.0.1'] }); // Health check endpoint server.get('/health', async (request, reply) => { return { status: 'ok', timestamp: new Date().toISOString() }; }); // Start server const start = async () => { try { await server.listen({ port: 3001, host: '0.0.0.0' }); } catch (err) { server.log.error(err); process.exit(1); } }; start();

Environment Configuration

# .env NODE_ENV=production PORT=3001 HOST=0.0.0.0 # Database DATABASE_URL=postgresql://user:password@localhost:5432/veltix # Redis REDIS_URL=redis://localhost:6379 # JWT JWT_SECRET=your-jwt-secret JWT_EXPIRES_IN=7d # API Keys API_KEY_SECRET=your-api-key-secret # CORS CORS_ORIGIN=http://localhost:3000,https://yourdomain.com # Rate Limiting RATE_LIMIT_MAX=100 RATE_LIMIT_WINDOW=60000

Authentication

JWT Authentication

// middleware/auth.ts import jwt from 'jsonwebtoken'; import { FastifyRequest, FastifyReply } from 'fastify'; interface JWTPayload { userId: string; email: string; role: string; } export const authenticateJWT = async ( request: FastifyRequest, reply: FastifyReply ) => { try { const token = request.headers.authorization?.replace('Bearer ', ''); if (!token) { return reply.status(401).send({ error: 'No token provided' }); } const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload; request.user = decoded; } catch (error) { return reply.status(401).send({ error: 'Invalid token' }); } }; // Protected route server.get('/api/dashboards', { preHandler: authenticateJWT }, async (request, reply) => { const user = request.user as JWTPayload; const dashboards = await getDashboardsByUserId(user.userId); return { dashboards }; });

API Key Authentication

// middleware/api-key.ts import { FastifyRequest, FastifyReply } from 'fastify'; export const authenticateAPIKey = async ( request: FastifyRequest, reply: FastifyReply ) => { const apiKey = request.headers['x-api-key'] as string; if (!apiKey) { return reply.status(401).send({ error: 'API key required' }); } // Validate API key const isValid = await validateAPIKey(apiKey); if (!isValid) { return reply.status(401).send({ error: 'Invalid API key' }); } request.apiKey = apiKey; }; // API key protected route server.get('/api/data', { preHandler: authenticateAPIKey }, async (request, reply) => { const apiKey = request.apiKey as string; const data = await getDataByAPIKey(apiKey); return { data }; });

Data Endpoints

Dashboard Endpoints

// routes/dashboards.ts import { FastifyInstance } from 'fastify'; export default async function dashboardRoutes(fastify: FastifyInstance) { // Get all dashboards fastify.get('/api/dashboards', async (request, reply) => { const dashboards = await fastify.prisma.dashboard.findMany({ where: { userId: request.user.userId } }); return { dashboards }; }); // Get dashboard by ID fastify.get('/api/dashboards/:id', async (request, reply) => { const { id } = request.params as { id: string }; const dashboard = await fastify.prisma.dashboard.findUnique({ where: { id }, include: { components: true } }); if (!dashboard) { return reply.status(404).send({ error: 'Dashboard not found' }); } return { dashboard }; }); // Create dashboard fastify.post('/api/dashboards', async (request, reply) => { const { title, layout, components } = request.body as any; const dashboard = await fastify.prisma.dashboard.create({ data: { title, layout, userId: request.user.userId, components: { create: components } } }); return { dashboard }; }); // Update dashboard fastify.put('/api/dashboards/:id', async (request, reply) => { const { id } = request.params as { id: string }; const updates = request.body as any; const dashboard = await fastify.prisma.dashboard.update({ where: { id }, data: updates }); return { dashboard }; }); // Delete dashboard fastify.delete('/api/dashboards/:id', async (request, reply) => { const { id } = request.params as { id: string }; await fastify.prisma.dashboard.delete({ where: { id } }); return { success: true }; }); }

Data Source Endpoints

// routes/data-sources.ts export default async function dataSourceRoutes(fastify: FastifyInstance) { // Get data from source fastify.get('/api/data-sources/:id/data', async (request, reply) => { const { id } = request.params as { id: string }; const { query } = request.query as { query?: string }; const dataSource = await fastify.prisma.dataSource.findUnique({ where: { id } }); if (!dataSource) { return reply.status(404).send({ error: 'Data source not found' }); } const data = await fetchDataFromSource(dataSource, query); return { data }; }); // Test data source connection fastify.post('/api/data-sources/test', async (request, reply) => { const config = request.body as any; try { const result = await testDataSourceConnection(config); return { success: true, result }; } catch (error) { return reply.status(400).send({ success: false, error: error.message }); } }); }

WebSocket Configuration

WebSocket Server

// websocket/server.ts import { WebSocketServer } from 'ws'; import { createServer } from 'http'; const server = createServer(); const wss = new WebSocketServer({ server }); wss.on('connection', (ws, request) => { console.log('Client connected'); // Authenticate connection const token = new URL(request.url!, `ws://${request.headers.host}`).searchParams.get('token'); if (!token) { ws.close(1008, 'Authentication required'); return; } try { const decoded = jwt.verify(token, process.env.JWT_SECRET!); ws.userId = decoded.userId; } catch (error) { ws.close(1008, 'Invalid token'); return; } // Handle messages ws.on('message', (message) => { try { const data = JSON.parse(message.toString()); handleWebSocketMessage(ws, data); } catch (error) { ws.send(JSON.stringify({ error: 'Invalid message format' })); } }); // Handle disconnection ws.on('close', () => { console.log('Client disconnected'); }); }); server.listen(3002, () => { console.log('WebSocket server running on port 3002'); });

Real-time Updates

// websocket/handlers.ts export const handleWebSocketMessage = (ws: WebSocket, data: any) => { switch (data.type) { case 'subscribe': handleSubscription(ws, data); break; case 'unsubscribe': handleUnsubscription(ws, data); break; case 'ping': ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() })); break; default: ws.send(JSON.stringify({ error: 'Unknown message type' })); } }; const handleSubscription = (ws: WebSocket, data: any) => { const { dashboardId, componentId } = data; // Add to subscription list if (!subscriptions.has(dashboardId)) { subscriptions.set(dashboardId, new Set()); } subscriptions.get(dashboardId)!.add(ws); ws.send(JSON.stringify({ type: 'subscribed', dashboardId, componentId })); }; // Broadcast updates to subscribers export const broadcastUpdate = (dashboardId: string, data: any) => { const subscribers = subscriptions.get(dashboardId); if (subscribers) { const message = JSON.stringify({ type: 'update', dashboardId, data }); subscribers.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { ws.send(message); } }); } };

Rate Limiting

Custom Rate Limiter

// middleware/rate-limiter.ts import { FastifyRequest, FastifyReply } from 'fastify'; import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); export const customRateLimiter = async ( request: FastifyRequest, reply: FastifyReply ) => { const key = `rate_limit:${request.ip}`; const limit = parseInt(process.env.RATE_LIMIT_MAX || '100'); const window = parseInt(process.env.RATE_LIMIT_WINDOW || '60000'); const current = await redis.incr(key); if (current === 1) { await redis.expire(key, window / 1000); } if (current > limit) { return reply.status(429).send({ error: 'Too many requests', retryAfter: Math.ceil(window / 1000) }); } reply.header('X-RateLimit-Limit', limit); reply.header('X-RateLimit-Remaining', Math.max(0, limit - current)); reply.header('X-RateLimit-Reset', Date.now() + window); }; // Apply to routes server.addHook('preHandler', customRateLimiter);

Error Handling

Global Error Handler

// middleware/error-handler.ts import { FastifyError, FastifyReply } from 'fastify'; export const errorHandler = ( error: FastifyError, request: FastifyRequest, reply: FastifyReply ) => { const statusCode = error.statusCode || 500; const message = error.message || 'Internal Server Error'; // Log error request.log.error(error); // Send error response reply.status(statusCode).send({ error: { message, code: error.code, statusCode } }); }; // Register error handler server.setErrorHandler(errorHandler);

Validation Errors

// schemas/dashboard.ts import { FastifySchema } from 'fastify'; export const createDashboardSchema: FastifySchema = { body: { type: 'object', required: ['title'], properties: { title: { type: 'string', minLength: 1, maxLength: 100 }, layout: { type: 'object' }, components: { type: 'array' } } }, response: { 200: { type: 'object', properties: { dashboard: { type: 'object', properties: { id: { type: 'string' }, title: { type: 'string' }, layout: { type: 'object' }, createdAt: { type: 'string' } } } } } } }; // Use schema server.post('/api/dashboards', { schema: createDashboardSchema, preHandler: authenticateJWT }, async (request, reply) => { // Handler implementation });

CORS Configuration

Advanced CORS Setup

// middleware/cors.ts import cors from '@fastify/cors'; export const corsConfig = { origin: (origin: string, callback: Function) => { const allowedOrigins = process.env.CORS_ORIGIN?.split(',') || [ 'http://localhost:3000' ]; if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: [ 'Content-Type', 'Authorization', 'X-API-Key', 'X-Requested-With' ], exposedHeaders: ['X-Total-Count', 'X-RateLimit-Remaining'], maxAge: 86400 // 24 hours }; // Register CORS await server.register(cors, corsConfig);

Logging

Structured Logging

// middleware/logging.ts import pino from 'pino'; export const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname' } } }); // Request logging middleware export const requestLogger = async (request: FastifyRequest, reply: FastifyReply) => { const startTime = Date.now(); reply.addHook('onResponse', (request, reply, done) => { const duration = Date.now() - startTime; logger.info({ method: request.method, url: request.url, statusCode: reply.statusCode, duration, userAgent: request.headers['user-agent'], ip: request.ip }); done(); }); };

Security

Security Headers

// middleware/security.ts import helmet from '@fastify/helmet'; export const securityConfig = { contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "ws:", "wss:"] } }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true } }; // Register security middleware await server.register(helmet, securityConfig);

Input Validation

// middleware/validation.ts import { FastifyRequest, FastifyReply } from 'fastify'; export const validateInput = (schema: any) => { return async (request: FastifyRequest, reply: FastifyReply) => { try { const { error } = schema.validate(request.body); if (error) { return reply.status(400).send({ error: 'Validation failed', details: error.details }); } } catch (error) { return reply.status(400).send({ error: 'Invalid input' }); } }; };

Testing

API Testing

// tests/api.test.ts import { test } from 'tap'; import { build } from '../src/app'; test('API endpoints', async (t) => { const app = await build(); // Test health endpoint const healthResponse = await app.inject({ method: 'GET', url: '/health' }); t.equal(healthResponse.statusCode, 200); t.same(JSON.parse(healthResponse.payload), { status: 'ok', timestamp: healthResponse.payload.timestamp }); // Test protected endpoint const protectedResponse = await app.inject({ method: 'GET', url: '/api/dashboards', headers: { authorization: 'Bearer invalid-token' } }); t.equal(protectedResponse.statusCode, 401); });

Configuration Examples

Production Configuration

// config/production.ts export const productionConfig = { server: { port: process.env.PORT || 3001, host: '0.0.0.0', trustProxy: true }, cors: { origin: process.env.CORS_ORIGIN?.split(',') || [], credentials: true }, rateLimit: { max: parseInt(process.env.RATE_LIMIT_MAX || '100'), timeWindow: parseInt(process.env.RATE_LIMIT_WINDOW || '60000') }, security: { jwtSecret: process.env.JWT_SECRET!, jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d' } };

Development Configuration

// config/development.ts export const developmentConfig = { server: { port: 3001, host: 'localhost', trustProxy: false }, cors: { origin: ['http://localhost:3000'], credentials: true }, rateLimit: { max: 1000, timeWindow: 60000 }, security: { jwtSecret: 'dev-secret', jwtExpiresIn: '30d' } };

Best Practices

1. Security

  • Use HTTPS in production
  • Implement proper authentication
  • Validate all inputs
  • Use rate limiting
  • Set security headers

2. Performance

  • Use connection pooling
  • Implement caching
  • Optimize database queries
  • Monitor response times

3. Reliability

  • Implement proper error handling
  • Use health checks
  • Monitor API usage
  • Set up logging

4. Maintainability

  • Use TypeScript
  • Write comprehensive tests
  • Document API endpoints
  • Follow consistent patterns
Last updated on