Files
podcastic/backend/src/index.ts
T
MichaelandClaude Haiku 4.5 1822ab1afe fix: single container without nginx — Express serves frontend
- Remove separate backend/ and frontend/ Dockerfiles
- Add root Dockerfile (3-stage: frontend build → backend build → runtime)
- Frontend static files served by Express from /public
- Simplify docker-compose to 3 services: mongodb, redis, app (port 3579)
- Fix vite proxy not to strip /api prefix from paths
- Default API URL to relative /api instead of hardcoded localhost

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-16 10:43:06 +02:00

103 lines
2.7 KiB
TypeScript

import express from 'express';
import cors from 'cors';
import 'dotenv/config';
import path from 'path';
import { fileURLToPath } from 'url';
import { connectDB } from './config/database';
import { initRedis, closeRedis } from './config/redis';
import { errorHandler } from './middleware/errorHandler';
import authRoutes from './routes/authRoutes';
import podcastRoutes from './routes/podcastRoutes';
import episodeRoutes from './routes/episodeRoutes';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ limit: '10mb', extended: true }));
// Initialize databases on startup
let isInitialized = false;
const initialize = async () => {
if (isInitialized) return;
try {
await connectDB();
await initRedis();
isInitialized = true;
console.log('✓ All services initialized');
} catch (error) {
console.error('✗ Initialization failed:', error);
process.exit(1);
}
};
// Health check endpoint (no auth required)
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// API Routes
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', message: 'API is running' });
});
app.use('/api/auth', authRoutes);
app.use('/api/podcasts', podcastRoutes);
app.use('/api/episodes', episodeRoutes);
// Serve frontend static files in production
const frontendDist = path.join(__dirname, '..', 'public');
app.use(express.static(frontendDist));
// SPA fallback — return index.html for any non-API route
app.get('*', (req, res) => {
const indexFile = path.join(frontendDist, 'index.html');
res.sendFile(indexFile, (err) => {
if (err) {
res.status(404).json({ success: false, message: 'Not found' });
}
});
});
// Error handler (must be last)
app.use(errorHandler);
// Graceful shutdown
const gracefulShutdown = async () => {
console.log('\nShutting down gracefully...');
await closeRedis();
process.exit(0);
};
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
// Start server
const startServer = async () => {
try {
await initialize();
app.listen(PORT, () => {
console.log(`\n${'='.repeat(50)}`);
console.log(`🎙️ Podcastic Backend`);
console.log(`Server running on http://localhost:${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(`${'='.repeat(50)}\n`);
});
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
};
startServer();
export default app;