typescript-express-starter
Version:
Quick and Easy TypeScript Express Starter
127 lines (109 loc) β’ 3.63 kB
text/typescript
import compression from 'compression';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import express from 'express';
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
import hpp from 'hpp';
import morgan from 'morgan';
import { NODE_ENV, PORT, LOG_FORMAT, CREDENTIALS, CORS_ORIGIN_LIST } from '@config/env';
import { Routes } from '@interfaces/routes.interface';
import { ErrorMiddleware } from '@middlewares/error.middleware';
import { logger, stream } from '@utils/logger';
class App {
public app: express.Application;
public env: string;
public port: string | number;
constructor(routes: Routes[], apiPrefix = '/api/v1') {
this.app = express();
this.env = NODE_ENV || 'development';
this.port = PORT || 3000;
this.initializeTrustProxy();
this.initializeMiddlewares();
this.initializeRoutes(routes, apiPrefix);
this.initializeErrorHandling();
}
public listen() {
const server = this.app.listen(this.port, () => {
logger.info(`=================================`);
logger.info(`======= ENV: ${this.env} =======`);
logger.info(`π App listening on the port ${this.port}`);
logger.info(`=================================`);
});
return server;
}
public getServer() {
return this.app;
}
private initializeTrustProxy() {
// Nginx, Heroku, Cloudflare λ± νλ‘μ νκ²½μμ μ€IP μΆμΆμ μν΄ νμ
this.app.set('trust proxy', 1);
}
private initializeMiddlewares() {
this.app.use(
rateLimit({
windowMs: 60_000,
limit: this.env === 'production' ? 100 : 1000,
standardHeaders: true,
legacyHeaders: false,
skip: (req) =>
this.env !== 'production' ||
['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.ip ?? ''),
}),
);
this.app.use(morgan(LOG_FORMAT || 'dev', { stream }));
// CORS νμ΄νΈλ¦¬μ€νΈλ₯Ό νκ²½λ³μμμ κ΄λ¦¬
const allowedOrigins =
CORS_ORIGIN_LIST.length > 0 ? CORS_ORIGIN_LIST : ['http://localhost:3000'];
this.app.use(
cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: CREDENTIALS,
}),
);
this.app.use(hpp());
this.app.use(
helmet({
contentSecurityPolicy:
this.env === 'production'
? {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
}
: false, // κ°λ° νκ²½μμλ CSP λΉνμ±ν (hot reload λ± νΈμ)
referrerPolicy: { policy: 'no-referrer' },
}),
);
this.app.use(compression());
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
this.app.use(cookieParser());
}
private initializeRoutes(routes: Routes[], apiPrefix: string) {
routes.forEach((route) => {
this.app.use(apiPrefix + route.path, route.router);
});
}
private initializeErrorHandling() {
// 404 μλ¬ μ²λ¦¬
this.app.use((req, res) => {
res.status(404).json({
statusCode: 404,
message: `Route ${req.originalUrl} not found`,
});
});
// μ μ μλ¬ μ²λ¦¬
this.app.use(ErrorMiddleware);
}
}
export default App;