← back to Cypress Awards
backend/src/server.js
120 lines
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const path = require('path');
const routes = require('./api/routes');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
connectSrc: ["'self'", "http://45.61.58.125:3010", "https://cypresawards.com", "http://localhost:3000", "http://localhost:3010"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", "https:"],
imgSrc: ["'self'", "data:", "https:", "http:"],
fontSrc: ["'self'", "https:", "data:"],
objectSrc: ["'none'"],
upgradeInsecureRequests: null
}
}
})); // Security headers
app.use(cors()); // Enable CORS
app.use(compression()); // Compress responses
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true }));
// Serve static frontend files with cache control
const frontendPath = path.join(__dirname, '../../frontend/src');
app.use(express.static(frontendPath, {
etag: false,
lastModified: false,
setHeaders: (res, path) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
}
}));
// Logging middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});
// API routes
app.use('/api', routes);
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Serve index.html for root path
app.get('/', (req, res) => {
res.sendFile(path.join(frontendPath, 'index.html'));
});
// API info endpoint
app.get('/api', (req, res) => {
res.json({
name: 'CyPresAwards API',
version: '1.0.0',
description: 'API for searching non-profit organizations with cy pres statements',
endpoints: {
organizations: '/api/organizations',
categories: '/api/categories',
legalTopics: '/api/legal-topics',
stats: '/api/stats',
health: '/health'
}
});
});
// 404 handler - serve index.html for client-side routing
app.use((req, res) => {
// If it's an API request, return JSON error
if (req.path.startsWith('/api/')) {
return res.status(404).json({
success: false,
error: 'Endpoint not found'
});
}
// Otherwise serve index.html for SPA routing
res.sendFile(path.join(frontendPath, 'index.html'));
});
// Error handler
app.use((err, req, res, next) => {
console.error('Server error:', err);
res.status(500).json({
success: false,
error: process.env.NODE_ENV === 'development' ? err.message : 'Internal server error'
});
});
// Start server
app.listen(PORT, () => {
console.log(`\n🚀 CyPresAwards API Server`);
console.log(`📡 Server running on http://localhost:${PORT}`);
console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(`\nAvailable endpoints:`);
console.log(` GET /api/organizations`);
console.log(` GET /api/organizations/:id`);
console.log(` GET /api/categories`);
console.log(` GET /api/legal-topics`);
console.log(` GET /api/stats`);
console.log(` GET /health\n`);
});
module.exports = app;