Skip to content
Open

#8 #29

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions templates/production-bkend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Production Backend Template

A production-ready backend template with TypeScript, Express, and enterprise features.

## Features

- ✅ TypeScript
- ✅ Express.js with security middleware
- ✅ JWT Authentication & RBAC
- ✅ Rate Limiting
- ✅ Request Logging
- ✅ Error Handling
- ✅ File Upload (PDF, Excel support)
- ✅ Monitoring Dashboard
- ✅ Multi-channel Notifications

## Quick Start

1. Install dependencies:
```bash
npm install
54 changes: 54 additions & 0 deletions templates/production-bkend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "production-backend",
"version": "1.0.0",
"description": "Production-ready backend with TypeScript, Express, and enterprise features",
"type": "module",
"main": "dist/server.js",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"clean": "rimraf dist",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"morgan": "^1.10.0",
"dotenv": "^16.3.1",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"express-rate-limit": "^7.1.5",
"multer": "^1.4.5-lts.1",
"xlsx": "^0.18.5",
"pdf-parse": "^1.1.1",
"nodemailer": "^6.9.7",
"twilio": "^4.19.0",
"axios": "^1.5.0",
"socket.io": "^4.7.4",
"chalk": "^5.3.0",
"winston": "^3.11.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/cors": "^2.8.17",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.5",
"@types/multer": "^1.4.11",
"@types/node": "^20.8.0",
"@types/nodemailer": "^6.4.14",
"typescript": "^5.2.2",
"tsx": "^4.6.0",
"rimraf": "^5.0.5"
},
"keywords": [
"express",
"typescript",
"backend",
"production",
"authentication",
"logging",
"monitoring"
]
}
Empty file.
Empty file.
Empty file.
60 changes: 60 additions & 0 deletions templates/production-bkend/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { config } from './config';
import { requestLogger } from './middleware/requestLogger';
import { globalRateLimiter } from './middleware/rateLimit';
import { errorHandler } from './middleware/errorHandler';
import routes from './routes';

const app = express();

// Security middleware
app.use(helmet());
app.use(cors({
origin: config.cors.origins,
credentials: true
}));

// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Logging middleware
if (config.nodeEnv === 'development') {
app.use(morgan('combined'));
}
app.use(requestLogger);

// Rate limiting
app.use(globalRateLimiter);

// Static files
app.use(express.static('public'));

// API routes
app.use('/api/v1', routes);

// Health check
app.get('/health', (req, res) => {
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: config.nodeEnv
});
});

// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Route not found',
path: req.originalUrl
});
});

// Error handling middleware (must be last)
app.use(errorHandler);

export default app;
53 changes: 53 additions & 0 deletions templates/production-bkend/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import dotenv from 'dotenv';

dotenv.config();

export const config = {
port: parseInt(process.env.PORT || '3000'),
nodeEnv: process.env.NODE_ENV || 'development',

cors: {
origins: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000']
},

database: {
url: process.env.DATABASE_URL || 'mongodb://localhost:27017/production-app'
},

jwt: {
secret: process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production',
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
},

rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.RATE_LIMIT_MAX || '100'),
loginMax: parseInt(process.env.RATE_LIMIT_LOGIN_MAX || '5')
},

upload: {
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedMimeTypes: [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/plain'
]
},

email: {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
from: process.env.EMAIL_FROM || 'noreply@yourapp.com'
},

twilio: {
accountSid: process.env.TWILIO_ACCOUNT_SID,
authToken: process.env.TWILIO_AUTH_TOKEN,
phoneNumber: process.env.TWILIO_PHONE_NUMBER
}
};
45 changes: 45 additions & 0 deletions templates/production-bkend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../config';
import { logger } from '../utils/logger';

export interface AuthRequest extends Request {
user?: any;
}

export const authenticate = (req: AuthRequest, res: Response, next: NextFunction) => {
const token = req.header('Authorization')?.replace('Bearer ', '');

if (!token) {
return res.status(401).json({ error: 'Access denied. No token provided.' });
}

try {
const decoded = jwt.verify(token, config.jwt.secret);
req.user = decoded;
next();
} catch (error) {
logger.error('Token verification failed', { error });
res.status(401).json({ error: 'Invalid token.' });
}
};

export const authorize = (...roles: string[]) => {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required.' });
}

const hasRole = roles.some(role => req.user.roles.includes(role));
if (!hasRole) {
logger.warn('Unauthorized access attempt', {
userId: req.user.id,
roles: req.user.roles,
requiredRoles: roles
});
return res.status(403).json({ error: 'Insufficient permissions.' });
}

next();
};
};
53 changes: 53 additions & 0 deletions templates/production-bkend/src/middleware/errorhandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';

export class AppError extends Error {
public statusCode: number;
public isOperational: boolean;

constructor(message: string, statusCode: number = 500, isOperational: boolean = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;

Error.captureStackTrace(this, this.constructor);
}
}

export const errorHandler = (
error: Error | AppError,
req: Request,
res: Response,
next: NextFunction
) => {
let statusCode = 500;
let message = 'Internal server error';

if (error instanceof AppError) {
statusCode = error.statusCode;
message = error.message;
} else if (error.name === 'ValidationError') {
statusCode = 400;
message = 'Validation error';
} else if (error.name === 'JsonWebTokenError') {
statusCode = 401;
message = 'Invalid token';
}

logger.error('Error handled by middleware', {
error: error.message,
url: req.url,
method: req.method,
ip: req.ip,
statusCode
});

res.status(statusCode).json({
error: message,
...(process.env.NODE_ENV === 'development' && { stack: error.stack })
});
};

export const asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
Empty file.
37 changes: 37 additions & 0 deletions templates/production-bkend/src/middleware/ratelimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import rateLimit from 'express-rate-limit';
import { config } from '../config';
import { logger } from '../utils/logger';

export const globalRateLimiter = rateLimit({
windowMs: config.rateLimit.windowMs,
max: config.rateLimit.max,
message: {
error: 'Too many requests from this IP, please try again later.'
},
handler: (req, res) => {
logger.warn('Rate limit exceeded', {
ip: req.ip,
url: req.url
});
res.status(429).json({
error: 'Too many requests from this IP, please try again later.'
});
}
});

export const authRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: config.rateLimit.loginMax,
message: {
error: 'Too many login attempts, please try again later.'
},
handler: (req, res) => {
logger.warn('Auth rate limit exceeded', {
ip: req.ip,
url: req.url
});
res.status(429).json({
error: 'Too many login attempts, please try again later.'
});
}
});
27 changes: 27 additions & 0 deletions templates/production-bkend/src/middleware/requestlogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';

export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;

const logData = {
method: req.method,
url: req.url,
statusCode: res.statusCode,
responseTime: `${duration}ms`,
ip: req.ip,
userAgent: req.get('User-Agent')
};

if (res.statusCode >= 400) {
logger.error('Request completed with error', logData);
} else {
logger.info('Request completed', logData);
}
});

next();
};
Loading