bitip/src/backend/app.ts
Tudor Stanciu 9ad0d9be93 feat: update version to 1.2.0 and refactor backend structure
- Changed main entry point from `index.js` to `server.js` in package.json.
- Created a new `app.ts` file to encapsulate Express app configuration.
- Removed old `index.ts` file and moved server logic to `server.ts`.
- Updated nodemon configurations to point to the new `server.ts`.
- Added new middleware for API key authentication with public endpoint support.
- Modified validators to accept any string for IPs, allowing handlers to determine validity.
- Added integration tests for batch lookup and health endpoints.
- Implemented unit tests for error handling and validation middleware.
- Updated `tsup` and `vitest` configurations to reflect new entry points and testing setup.
2025-10-13 01:14:37 +03:00

104 lines
3.2 KiB
TypeScript

// Copyright (c) 2025 Tudor Stanciu
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import path from 'path';
import apiRoutes from './routes/api';
import apiKeyAuth from './middleware/auth';
import dynamicRateLimit from './middleware/rateLimit';
import { errorHandler } from './middleware/errorHandler';
import config from './services/config';
import logger from './services/logger';
import { generateRuntimeConfig } from './services/runtimeConfig';
import { healthCheckHandler } from './handlers/healthHandler';
import { paths } from './utils/paths';
/**
* Creates and configures the Express application
* This factory function allows the app to be created without starting the server
* Useful for testing and modular architecture
*/
export const createApp = (): express.Express => {
const app = express();
// Security middleware
app.use(
helmet({
contentSecurityPolicy: config.enableHttpsSecurity
? {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https://unpkg.com'],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
}
: false,
strictTransportSecurity: config.enableHttpsSecurity,
crossOriginOpenerPolicy: config.enableHttpsSecurity,
crossOriginResourcePolicy: config.enableHttpsSecurity,
originAgentCluster: false, // Disable Origin-Agent-Cluster header to avoid browser warnings
})
);
app.use(
cors({
origin: process.env.NODE_ENV === 'production' ? false : true,
credentials: true,
})
);
app.use(compression());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Request logging
app.use((req, res, next) => {
logger.debug('Incoming request', {
method: req.method,
path: req.path,
ip: req.ip,
userAgent: req.headers['user-agent'],
});
next();
});
// Apply base path if configured
const basePath = config.basePath.endsWith('/')
? config.basePath.slice(0, -1)
: config.basePath;
// Serve static frontend files
const frontendPath = paths.frontendDir;
// Generate runtime configuration (env.js) at startup
generateRuntimeConfig(frontendPath, basePath);
// Health check endpoint at root (always accessible for Docker HEALTHCHECK)
app.get('/api/health', healthCheckHandler);
// API routes with authentication and rate limiting
app.use(`${basePath}/api`, apiKeyAuth, dynamicRateLimit, apiRoutes);
// Serve static frontend files
app.use(basePath, express.static(frontendPath));
// Fallback to index.html for client-side routing (Express 5 syntax)
// Named wildcard required in path-to-regexp v8: /*path matches any path
app.get(`${basePath}/*path`, (_req, res): void => {
res.sendFile(path.join(frontendPath, 'index.html'));
});
// Global error handler (must be last middleware)
app.use(errorHandler);
return app;
};