UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,911 lines (1,606 loc) 62.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.tinyhttpTemplate = void 0; exports.tinyhttpTemplate = { id: 'tinyhttp', name: 'tinyhttp', displayName: 'Tinyhttp', description: 'Modern Express-like web framework with ES modules, TypeScript-first design, and 35% less overhead', language: 'typescript', framework: 'tinyhttp', version: '2.2.2', tags: ['nodejs', 'tinyhttp', 'api', 'rest', 'modern', 'esm', 'typescript', 'fast'], port: 3000, dependencies: {}, features: ['esm', 'typescript', 'middleware', 'routing', 'cors', 'authentication', 'validation', 'websocket', 'mongodb', 'rate-limiting', 'testing'], files: { // TypeScript project configuration with ES modules 'package.json': `{ "name": "{{projectName}}", "version": "1.0.0", "description": "Modern Tinyhttp API server with TypeScript and ES modules", "type": "module", "main": "dist/index.js", "engines": { "node": ">=18.0.0" }, "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js", "lint": "eslint src --ext .ts", "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", "typecheck": "tsc --noEmit", "format": "prettier --write .", "docker:build": "docker build -t {{projectName}} .", "docker:run": "docker run -p 3000:3000 {{projectName}}" }, "dependencies": { "@tinyhttp/app": "^2.2.2", "@tinyhttp/cors": "^2.0.0", "@tinyhttp/logger": "^2.0.0", "@tinyhttp/cookie-parser": "^2.1.0", "@tinyhttp/session": "^2.1.0", "@tinyhttp/jwt": "^2.1.0", "@tinyhttp/etag": "^2.1.0", "@tinyhttp/compression": "^2.0.0", "@tinyhttp/rate-limit": "^2.0.2", "@tinyhttp/helmet": "^2.0.0", "@tinyhttp/unless": "^2.1.1", "milliparsec": "^2.3.0", "sirv": "^2.0.4", "eta": "^3.4.0", "mongodb": "^6.5.0", "mongoose": "^8.3.1", "bcryptjs": "^2.4.3", "jsonwebtoken": "^9.0.2", "zod": "^3.22.4", "dotenv": "^16.4.5", "pino": "^8.19.0", "pino-pretty": "^11.0.0", "ioredis": "^5.3.2", "ws": "^8.16.0", "@tinyhttp/ws": "^0.2.30", "nanoid": "^5.0.7", "dayjs": "^1.11.10", "node-cron": "^3.0.3" }, "devDependencies": { "@types/node": "^20.12.7", "@types/bcryptjs": "^2.4.6", "@types/jsonwebtoken": "^9.0.6", "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "prettier": "^3.2.5", "typescript": "^5.4.5", "tsx": "^4.7.2", "vitest": "^1.5.0", "@vitest/coverage-v8": "^1.5.0", "@vitest/ui": "^1.5.0", "supertest": "^7.0.0", "@types/supertest": "^6.0.2" } }`, // TypeScript configuration for ES modules 'tsconfig.json': `{ "compilerOptions": { "target": "ES2022", "module": "ES2022", "lib": ["ES2022"], "moduleResolution": "bundler", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "removeComments": true, "noEmitOnError": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@/*": ["src/*"], "@config/*": ["src/config/*"], "@controllers/*": ["src/controllers/*"], "@middlewares/*": ["src/middlewares/*"], "@models/*": ["src/models/*"], "@routes/*": ["src/routes/*"], "@services/*": ["src/services/*"], "@utils/*": ["src/utils/*"], "@types/*": ["src/types/*"] }, "types": ["vitest/globals"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "coverage"] }`, // Main application entry point 'src/index.ts': `import { App } from '@tinyhttp/app'; import { cors } from '@tinyhttp/cors'; import { logger } from '@tinyhttp/logger'; import { cookieParser } from '@tinyhttp/cookie-parser'; import { compression } from '@tinyhttp/compression'; import { helmet } from '@tinyhttp/helmet'; import { rateLimit } from '@tinyhttp/rate-limit'; import { json } from 'milliparsec'; import { createServer } from 'http'; import { WebSocketServer } from 'ws'; import { config } from 'dotenv'; import { connectDatabase } from './config/database.js'; import { connectRedis } from './config/redis.js'; import { setupWebSocket } from './config/websocket.js'; import { errorHandler } from './middlewares/error.middleware.js'; import { notFoundHandler } from './middlewares/notFound.middleware.js'; import { requestLogger } from './middlewares/logger.middleware.js'; import { logger as log } from './utils/logger.js'; import authRoutes from './routes/auth.routes.js'; import userRoutes from './routes/user.routes.js'; import todoRoutes from './routes/todo.routes.js'; // Load environment variables config(); const app = new App(); const PORT = process.env.PORT || 3000; // Create HTTP server const server = createServer(); // Create WebSocket server const wss = new WebSocketServer({ server }); // Setup WebSocket setupWebSocket(wss); // Security middleware app.use(helmet()); app.use(cors({ origin: process.env.CORS_ORIGIN?.split(',') || '*', credentials: true })); // Body parsing middleware app.use(json()); app.use(cookieParser()); // Compression middleware app.use(compression()); // Request logging app.use(logger({ timestamp: { format: 'HH:mm:ss' }, colorize: true, emoji: true })); app.use(requestLogger); // Rate limiting app.use('/api', rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, message: 'Too many requests from this IP' })); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), environment: process.env.NODE_ENV || 'development' }); }); // API info endpoint app.get('/api/v1', (req, res) => { res.json({ message: '{{projectName}} API', version: '1.0.0', framework: 'Tinyhttp', performance: '35% faster than Express', endpoints: { auth: '/api/v1/auth', users: '/api/v1/users', todos: '/api/v1/todos', websocket: 'ws://localhost:3000', health: '/health' } }); }); // API routes app.use('/api/v1/auth', authRoutes); app.use('/api/v1/users', userRoutes); app.use('/api/v1/todos', todoRoutes); // 404 handler app.use(notFoundHandler); // Global error handler app.use(errorHandler); // Attach app to server server.on('request', app.handler.bind(app)); // Graceful shutdown process.on('SIGTERM', async () => { log.info('SIGTERM signal received: closing HTTP server'); server.close(() => { log.info('HTTP server closed'); }); // Close WebSocket connections wss.clients.forEach((client) => { client.close(); }); process.exit(0); }); // Start server const startServer = async () => { try { // Connect to MongoDB await connectDatabase(); // Connect to Redis await connectRedis(); server.listen(PORT, () => { log.info(\`🚀 Server is running on port \${PORT}\`); log.info(\`⚡ Using Tinyhttp - 35% faster than Express\`); log.info(\`📦 ES Modules enabled\`); log.info(\`🔧 Environment: \${process.env.NODE_ENV || 'development'}\`); }); } catch (error) { log.error('Failed to start server:', error); process.exit(1); } }; startServer(); export { app, wss };`, // Authentication routes 'src/routes/auth.routes.ts': `import { App } from '@tinyhttp/app'; import { z } from 'zod'; import { validate } from '../middlewares/validate.middleware.js'; import { AuthController } from '../controllers/auth.controller.js'; import { authenticate } from '../middlewares/auth.middleware.js'; const router = new App(); const authController = new AuthController(); // Validation schemas const registerSchema = z.object({ body: z.object({ email: z.string().email(), password: z.string().min(8), name: z.string().min(1) }) }); const loginSchema = z.object({ body: z.object({ email: z.string().email(), password: z.string() }) }); const forgotPasswordSchema = z.object({ body: z.object({ email: z.string().email() }) }); const resetPasswordSchema = z.object({ params: z.object({ token: z.string() }), body: z.object({ password: z.string().min(8) }) }); // Routes router.post('/register', validate(registerSchema), authController.register); router.post('/login', validate(loginSchema), authController.login); router.post('/refresh', authController.refreshToken); router.post('/logout', authenticate, authController.logout); router.get('/verify/:token', authController.verifyEmail); router.post('/forgot-password', validate(forgotPasswordSchema), authController.forgotPassword); router.post('/reset-password/:token', validate(resetPasswordSchema), authController.resetPassword); export default router;`, // User routes 'src/routes/user.routes.ts': `import { App } from '@tinyhttp/app'; import { z } from 'zod'; import { validate } from '../middlewares/validate.middleware.js'; import { authenticate, authorize } from '../middlewares/auth.middleware.js'; import { UserController } from '../controllers/user.controller.js'; const router = new App(); const userController = new UserController(); // Validation schemas const updateUserSchema = z.object({ params: z.object({ id: z.string() }), body: z.object({ email: z.string().email().optional(), name: z.string().min(1).optional() }) }); const changePasswordSchema = z.object({ body: z.object({ currentPassword: z.string(), newPassword: z.string().min(8) }) }); // Routes router.get('/', authenticate, authorize('admin'), userController.getAllUsers); router.get('/me', authenticate, userController.getCurrentUser); router.get('/:id', authenticate, userController.getUserById); router.put('/:id', authenticate, validate(updateUserSchema), userController.updateUser); router.delete('/:id', authenticate, authorize('admin'), userController.deleteUser); router.post('/change-password', authenticate, validate(changePasswordSchema), userController.changePassword); router.post('/avatar', authenticate, userController.uploadAvatar); export default router;`, // Todo routes 'src/routes/todo.routes.ts': `import { App } from '@tinyhttp/app'; import { z } from 'zod'; import { validate } from '../middlewares/validate.middleware.js'; import { authenticate } from '../middlewares/auth.middleware.js'; import { TodoController } from '../controllers/todo.controller.js'; const router = new App(); const todoController = new TodoController(); // Apply authentication to all routes router.use(authenticate); // Validation schemas const createTodoSchema = z.object({ body: z.object({ title: z.string().min(1), description: z.string().optional(), priority: z.enum(['low', 'medium', 'high']).optional(), dueDate: z.string().datetime().optional() }) }); const updateTodoSchema = z.object({ params: z.object({ id: z.string() }), body: z.object({ title: z.string().min(1).optional(), description: z.string().optional(), status: z.enum(['pending', 'in_progress', 'completed']).optional(), priority: z.enum(['low', 'medium', 'high']).optional(), dueDate: z.string().datetime().optional() }) }); const querySchema = z.object({ query: z.object({ page: z.coerce.number().min(1).optional(), limit: z.coerce.number().min(1).max(100).optional(), status: z.enum(['pending', 'in_progress', 'completed']).optional(), priority: z.enum(['low', 'medium', 'high']).optional() }) }); // Routes router.get('/', validate(querySchema), todoController.getAllTodos); router.get('/:id', todoController.getTodoById); router.post('/', validate(createTodoSchema), todoController.createTodo); router.put('/:id', validate(updateTodoSchema), todoController.updateTodo); router.delete('/:id', todoController.deleteTodo); router.post('/bulk/delete', todoController.bulkDelete); router.post('/bulk/update', todoController.bulkUpdate); export default router;`, // Authentication controller 'src/controllers/auth.controller.ts': `import { Request, Response } from '@tinyhttp/app'; import { AuthService } from '../services/auth.service.js'; import { EmailService } from '../services/email.service.js'; import { logger } from '../utils/logger.js'; import { asyncHandler } from '../utils/asyncHandler.js'; export class AuthController { private authService: AuthService; private emailService: EmailService; constructor() { this.authService = new AuthService(); this.emailService = new EmailService(); } register = asyncHandler(async (req: Request, res: Response) => { const { email, password, name } = req.body; const result = await this.authService.register({ email, password, name }); // Send verification email await this.emailService.sendVerificationEmail(email, result.verificationToken); res.status(201).json({ success: true, message: 'Registration successful. Please check your email to verify your account.', data: { user: result.user, accessToken: result.accessToken, refreshToken: result.refreshToken } }); }); login = asyncHandler(async (req: Request, res: Response) => { const { email, password } = req.body; const result = await this.authService.login(email, password); // Set refresh token as HTTP-only cookie res.cookie('refreshToken', result.refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days }); res.json({ success: true, message: 'Login successful', data: { user: result.user, accessToken: result.accessToken } }); }); refreshToken = asyncHandler(async (req: Request, res: Response) => { const refreshToken = req.cookies?.refreshToken || req.body.refreshToken; if (!refreshToken) { res.status(401); throw new Error('Refresh token not provided'); } const result = await this.authService.refreshToken(refreshToken); res.json({ success: true, data: { accessToken: result.accessToken } }); }); logout = asyncHandler(async (req: Request, res: Response) => { const userId = req.user?.id; if (userId) { await this.authService.logout(userId); } res.clearCookie('refreshToken'); res.json({ success: true, message: 'Logout successful' }); }); verifyEmail = asyncHandler(async (req: Request, res: Response) => { const { token } = req.params; await this.authService.verifyEmail(token); res.json({ success: true, message: 'Email verified successfully' }); }); forgotPassword = asyncHandler(async (req: Request, res: Response) => { const { email } = req.body; const resetToken = await this.authService.forgotPassword(email); // Send reset email await this.emailService.sendPasswordResetEmail(email, resetToken); res.json({ success: true, message: 'Password reset email sent' }); }); resetPassword = asyncHandler(async (req: Request, res: Response) => { const { token } = req.params; const { password } = req.body; await this.authService.resetPassword(token, password); res.json({ success: true, message: 'Password reset successful' }); }); }`, // User controller 'src/controllers/user.controller.ts': `import { Request, Response } from '@tinyhttp/app'; import { UserService } from '../services/user.service.js'; import { asyncHandler } from '../utils/asyncHandler.js'; import { uploadSingle } from '../utils/upload.js'; export class UserController { private userService: UserService; constructor() { this.userService = new UserService(); } getAllUsers = asyncHandler(async (req: Request, res: Response) => { const { page = 1, limit = 10, search } = req.query; const result = await this.userService.getAllUsers({ page: Number(page), limit: Number(limit), search: search as string }); res.json({ success: true, data: result }); }); getCurrentUser = asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; const user = await this.userService.getUserById(userId); res.json({ success: true, data: user }); }); getUserById = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const user = await this.userService.getUserById(id); res.json({ success: true, data: user }); }); updateUser = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const updates = req.body; // Ensure users can only update their own profile unless admin if (req.user!.id !== id && req.user!.role !== 'admin') { res.status(403); throw new Error('Forbidden'); } const user = await this.userService.updateUser(id, updates); res.json({ success: true, message: 'User updated successfully', data: user }); }); deleteUser = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; await this.userService.deleteUser(id); res.json({ success: true, message: 'User deleted successfully' }); }); changePassword = asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; const { currentPassword, newPassword } = req.body; await this.userService.changePassword(userId, currentPassword, newPassword); res.json({ success: true, message: 'Password changed successfully' }); }); uploadAvatar = [ uploadSingle('avatar'), asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; if (!req.file) { res.status(400); throw new Error('No file uploaded'); } const avatarUrl = await this.userService.updateAvatar(userId, req.file); res.json({ success: true, message: 'Avatar uploaded successfully', data: { avatarUrl } }); }) ]; }`, // Todo controller 'src/controllers/todo.controller.ts': `import { Request, Response } from '@tinyhttp/app'; import { TodoService } from '../services/todo.service.js'; import { asyncHandler } from '../utils/asyncHandler.js'; import { broadcastToUser } from '../config/websocket.js'; export class TodoController { private todoService: TodoService; constructor() { this.todoService = new TodoService(); } getAllTodos = asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; const { page = 1, limit = 10, status, priority } = req.query; const result = await this.todoService.getAllTodos({ userId, page: Number(page), limit: Number(limit), status: status as string, priority: priority as string }); res.json({ success: true, data: result }); }); getTodoById = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const userId = req.user!.id; const todo = await this.todoService.getTodoById(id, userId); res.json({ success: true, data: todo }); }); createTodo = asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; const todoData = { ...req.body, userId }; const todo = await this.todoService.createTodo(todoData); // Broadcast to WebSocket clients broadcastToUser(userId, 'todo:created', todo); res.status(201).json({ success: true, message: 'Todo created successfully', data: todo }); }); updateTodo = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const userId = req.user!.id; const updates = req.body; const todo = await this.todoService.updateTodo(id, userId, updates); // Broadcast to WebSocket clients broadcastToUser(userId, 'todo:updated', todo); res.json({ success: true, message: 'Todo updated successfully', data: todo }); }); deleteTodo = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const userId = req.user!.id; await this.todoService.deleteTodo(id, userId); // Broadcast to WebSocket clients broadcastToUser(userId, 'todo:deleted', { id }); res.json({ success: true, message: 'Todo deleted successfully' }); }); bulkDelete = asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; const { ids } = req.body; if (!Array.isArray(ids) || ids.length === 0) { res.status(400); throw new Error('Invalid todo IDs'); } const count = await this.todoService.bulkDelete(ids, userId); // Broadcast to WebSocket clients broadcastToUser(userId, 'todos:bulk-deleted', { ids, count }); res.json({ success: true, message: \`\${count} todos deleted successfully\` }); }); bulkUpdate = asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.id; const { ids, updates } = req.body; if (!Array.isArray(ids) || ids.length === 0) { res.status(400); throw new Error('Invalid todo IDs'); } const count = await this.todoService.bulkUpdate(ids, userId, updates); // Broadcast to WebSocket clients broadcastToUser(userId, 'todos:bulk-updated', { ids, updates, count }); res.json({ success: true, message: \`\${count} todos updated successfully\` }); }); }`, // Authentication middleware 'src/middlewares/auth.middleware.ts': `import { Request, Response, NextHandler } from '@tinyhttp/app'; import jwt from 'jsonwebtoken'; import { UserService } from '../services/user.service.js'; interface JwtPayload { id: string; email: string; role: string; } declare module '@tinyhttp/app' { interface Request { user?: JwtPayload; } } const userService = new UserService(); export const authenticate = async (req: Request, res: Response, next: NextHandler) => { try { let token: string | undefined; // Check for token in Authorization header if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { token = req.headers.authorization.split(' ')[1]; } if (!token) { res.status(401); throw new Error('Not authorized, no token'); } // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload; // Check if user still exists const user = await userService.getUserById(decoded.id); if (!user) { res.status(401); throw new Error('User no longer exists'); } // Attach user to request req.user = { id: decoded.id, email: decoded.email, role: decoded.role }; next(); } catch (error) { res.status(401).json({ success: false, error: { message: 'Not authorized', status: 401 } }); } }; export const authorize = (...roles: string[]) => { return (req: Request, res: Response, next: NextHandler) => { if (!req.user) { return res.status(401).json({ success: false, error: { message: 'Not authenticated', status: 401 } }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, error: { message: 'Not authorized for this resource', status: 403 } }); } next(); }; };`, // Error handling middleware 'src/middlewares/error.middleware.ts': `import { Request, Response, NextHandler } from '@tinyhttp/app'; import { logger } from '../utils/logger.js'; interface ErrorWithStatus extends Error { status?: number; code?: string; } export const errorHandler = ( err: ErrorWithStatus, req: Request, res: Response, next: NextHandler ) => { let status = err.status || res.statusCode || 500; let message = err.message || 'Internal Server Error'; // MongoDB duplicate key error if (err.code === '11000') { status = 400; message = 'Duplicate field value'; } // JWT errors if (err.name === 'JsonWebTokenError') { status = 401; message = 'Invalid token'; } if (err.name === 'TokenExpiredError') { status = 401; message = 'Token expired'; } // Zod validation errors if (err.name === 'ZodError') { status = 400; message = 'Validation error'; } // Log error logger.error({ message: err.message, stack: err.stack, url: req.url, method: req.method, ip: req.ip, status }); res.status(status).json({ success: false, error: { message, status, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) } }); };`, // Not found middleware 'src/middlewares/notFound.middleware.ts': `import { Request, Response } from '@tinyhttp/app'; export const notFoundHandler = (req: Request, res: Response) => { res.status(404).json({ success: false, error: { message: 'Resource not found', status: 404, path: req.originalUrl } }); };`, // Validation middleware 'src/middlewares/validate.middleware.ts': `import { Request, Response, NextHandler } from '@tinyhttp/app'; import { z, ZodError, ZodSchema } from 'zod'; export const validate = (schema: ZodSchema) => { return async (req: Request, res: Response, next: NextHandler) => { try { await schema.parseAsync({ body: req.body, query: req.query, params: req.params }); next(); } catch (error) { if (error instanceof ZodError) { res.status(400).json({ success: false, error: { message: 'Validation failed', status: 400, details: error.errors } }); } else { next(error); } } }; };`, // Logger middleware 'src/middlewares/logger.middleware.ts': `import { Request, Response, NextHandler } from '@tinyhttp/app'; import { logger } from '../utils/logger.js'; import { nanoid } from 'nanoid'; export const requestLogger = (req: Request, res: Response, next: NextHandler) => { const requestId = nanoid(10); const start = Date.now(); // Attach request ID req.id = requestId; // Log request logger.info({ requestId, method: req.method, url: req.url, ip: req.ip, userAgent: req.headers['user-agent'] }); // Log response const originalSend = res.send; res.send = function (data: any) { const duration = Date.now() - start; logger.info({ requestId, method: req.method, url: req.url, status: res.statusCode, duration: \`\${duration}ms\` }); return originalSend.call(this, data); }; next(); };`, // MongoDB configuration 'src/config/database.ts': `import mongoose from 'mongoose'; import { logger } from '../utils/logger.js'; export const connectDatabase = async () => { try { const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/{{projectName}}'; await mongoose.connect(mongoUri, { serverSelectionTimeoutMS: 5000, maxPoolSize: 10 }); logger.info('MongoDB connected successfully'); // Handle connection events mongoose.connection.on('disconnected', () => { logger.warn('MongoDB disconnected'); }); mongoose.connection.on('error', (err) => { logger.error('MongoDB connection error:', err); }); } catch (error) { logger.error('MongoDB connection failed:', error); throw error; } }; // Graceful shutdown export const disconnectDatabase = async () => { try { await mongoose.disconnect(); logger.info('MongoDB disconnected'); } catch (error) { logger.error('Error disconnecting MongoDB:', error); } };`, // User model 'src/models/user.model.ts': `import mongoose, { Schema, Document } from 'mongoose'; import bcrypt from 'bcryptjs'; export interface IUser extends Document { email: string; password: string; name: string; role: 'user' | 'admin'; avatar?: string; isEmailVerified: boolean; verificationToken?: string; resetToken?: string; resetTokenExpiry?: Date; refreshTokens: string[]; createdAt: Date; updatedAt: Date; comparePassword(candidatePassword: string): Promise<boolean>; } const userSchema = new Schema<IUser>({ email: { type: String, required: true, unique: true, lowercase: true, trim: true }, password: { type: String, required: true }, name: { type: String, required: true, trim: true }, role: { type: String, enum: ['user', 'admin'], default: 'user' }, avatar: String, isEmailVerified: { type: Boolean, default: false }, verificationToken: String, resetToken: String, resetTokenExpiry: Date, refreshTokens: [{ type: String }] }, { timestamps: true }); // Index for better query performance userSchema.index({ email: 1 }); // Hash password before saving userSchema.pre('save', async function(next) { if (!this.isModified('password')) return next(); const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); }); // Compare password method userSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> { return bcrypt.compare(candidatePassword, this.password); }; // Remove sensitive fields from JSON userSchema.methods.toJSON = function() { const obj = this.toObject(); delete obj.password; delete obj.refreshTokens; delete obj.verificationToken; delete obj.resetToken; delete obj.resetTokenExpiry; return obj; }; export const User = mongoose.model<IUser>('User', userSchema);`, // Todo model 'src/models/todo.model.ts': `import mongoose, { Schema, Document } from 'mongoose'; export interface ITodo extends Document { title: string; description?: string; status: 'pending' | 'in_progress' | 'completed'; priority: 'low' | 'medium' | 'high'; dueDate?: Date; userId: mongoose.Types.ObjectId; createdAt: Date; updatedAt: Date; } const todoSchema = new Schema<ITodo>({ title: { type: String, required: true, trim: true }, description: { type: String, trim: true }, status: { type: String, enum: ['pending', 'in_progress', 'completed'], default: 'pending' }, priority: { type: String, enum: ['low', 'medium', 'high'], default: 'medium' }, dueDate: Date, userId: { type: Schema.Types.ObjectId, ref: 'User', required: true } }, { timestamps: true }); // Indexes for better query performance todoSchema.index({ userId: 1, status: 1 }); todoSchema.index({ userId: 1, priority: 1 }); todoSchema.index({ userId: 1, dueDate: 1 }); export const Todo = mongoose.model<ITodo>('Todo', todoSchema);`, // Redis configuration 'src/config/redis.ts': `import Redis from 'ioredis'; import { logger } from '../utils/logger.js'; const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'; export const redis = new Redis(redisUrl, { maxRetriesPerRequest: 3, retryStrategy: (times) => { const delay = Math.min(times * 50, 2000); return delay; }, reconnectOnError: (err) => { const targetError = 'READONLY'; if (err.message.includes(targetError)) { return true; } return false; } }); redis.on('connect', () => { logger.info('Redis client connected'); }); redis.on('error', (err) => { logger.error('Redis client error:', err); }); redis.on('ready', () => { logger.info('Redis client ready'); }); export const connectRedis = async () => { try { await redis.ping(); logger.info('Redis connection verified'); } catch (error) { logger.error('Redis connection failed:', error); throw error; } }; // Cache utilities export const cache = { async get<T>(key: string): Promise<T | null> { const value = await redis.get(key); return value ? JSON.parse(value) : null; }, async set(key: string, value: any, ttl?: number): Promise<void> { const serialized = JSON.stringify(value); if (ttl) { await redis.setex(key, ttl, serialized); } else { await redis.set(key, serialized); } }, async del(key: string): Promise<void> { await redis.del(key); }, async delPattern(pattern: string): Promise<void> { const keys = await redis.keys(pattern); if (keys.length > 0) { await redis.del(...keys); } } };`, // WebSocket configuration 'src/config/websocket.ts': `import { WebSocketServer, WebSocket } from 'ws'; import jwt from 'jsonwebtoken'; import { logger } from '../utils/logger.js'; import { parse } from 'url'; interface AuthenticatedWebSocket extends WebSocket { userId?: string; isAlive?: boolean; } const clients = new Map<string, Set<AuthenticatedWebSocket>>(); export const setupWebSocket = (wss: WebSocketServer) => { // Heartbeat interval const interval = setInterval(() => { wss.clients.forEach((ws: AuthenticatedWebSocket) => { if (ws.isAlive === false) { return ws.terminate(); } ws.isAlive = false; ws.ping(); }); }, 30000); wss.on('connection', (ws: AuthenticatedWebSocket, req) => { try { // Parse token from query string const { query } = parse(req.url || '', true); const token = query.token as string; if (!token) { ws.close(1008, 'Missing authentication token'); return; } // Verify JWT token const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any; ws.userId = decoded.id; // Add to clients map if (!clients.has(ws.userId)) { clients.set(ws.userId, new Set()); } clients.get(ws.userId)!.add(ws); // Setup heartbeat ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; }); logger.info(\`WebSocket client connected: \${ws.userId}\`); // Handle messages ws.on('message', (data) => { try { const message = JSON.parse(data.toString()); handleWebSocketMessage(ws, message); } catch (error) { logger.error('Invalid WebSocket message:', error); } }); // Handle close ws.on('close', () => { if (ws.userId && clients.has(ws.userId)) { clients.get(ws.userId)!.delete(ws); if (clients.get(ws.userId)!.size === 0) { clients.delete(ws.userId); } } logger.info(\`WebSocket client disconnected: \${ws.userId}\`); }); // Send welcome message ws.send(JSON.stringify({ type: 'connected', userId: ws.userId, timestamp: new Date().toISOString() })); } catch (error) { logger.error('WebSocket authentication failed:', error); ws.close(1008, 'Invalid authentication token'); } }); wss.on('close', () => { clearInterval(interval); }); }; // Handle incoming WebSocket messages const handleWebSocketMessage = (ws: AuthenticatedWebSocket, message: any) => { const { type, data } = message; switch (type) { case 'ping': ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() })); break; case 'subscribe': // Handle subscription to specific events logger.info(\`User \${ws.userId} subscribed to: \${data.event}\`); break; default: logger.warn(\`Unknown WebSocket message type: \${type}\`); } }; // Broadcast to specific user export const broadcastToUser = (userId: string, event: string, data: any) => { const userClients = clients.get(userId); if (userClients) { const message = JSON.stringify({ type: event, data, timestamp: Date.now() }); userClients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(message); } }); } }; // Broadcast to all connected clients export const broadcast = (event: string, data: any) => { const message = JSON.stringify({ type: event, data, timestamp: Date.now() }); clients.forEach((userClients) => { userClients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(message); } }); }); };`, // Authentication service 'src/services/auth.service.ts': `import jwt from 'jsonwebtoken'; import { nanoid } from 'nanoid'; import { User, IUser } from '../models/user.model.js'; import { redis } from '../config/redis.js'; import { logger } from '../utils/logger.js'; export class AuthService { async register(data: { email: string; password: string; name: string }) { // Check if user exists const existingUser = await User.findOne({ email: data.email }); if (existingUser) { throw new Error('User already exists'); } // Create verification token const verificationToken = nanoid(32); // Create user const user = await User.create({ ...data, verificationToken }); // Generate tokens const accessToken = this.generateAccessToken(user); const refreshToken = this.generateRefreshToken(user); // Save refresh token user.refreshTokens.push(refreshToken); await user.save(); return { user: user.toJSON(), accessToken, refreshToken, verificationToken }; } async login(email: string, password: string) { // Find user const user = await User.findOne({ email }); if (!user) { throw new Error('Invalid credentials'); } // Check password const isPasswordValid = await user.comparePassword(password); if (!isPasswordValid) { throw new Error('Invalid credentials'); } // Check if email is verified if (!user.isEmailVerified) { throw new Error('Please verify your email first'); } // Generate tokens const accessToken = this.generateAccessToken(user); const refreshToken = this.generateRefreshToken(user); // Save refresh token user.refreshTokens.push(refreshToken); await user.save(); // Cache user data await redis.setex(\`user:\${user.id}\`, 3600, JSON.stringify(user.toJSON())); return { user: user.toJSON(), accessToken, refreshToken }; } async refreshToken(refreshToken: string) { try { // Verify refresh token const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as any; // Find user and check if refresh token exists const user = await User.findById(decoded.id); if (!user || !user.refreshTokens.includes(refreshToken)) { throw new Error('Invalid refresh token'); } // Generate new access token const accessToken = this.generateAccessToken(user); return { accessToken }; } catch (error) { throw new Error('Invalid refresh token'); } } async logout(userId: string) { // Remove all refresh tokens await User.findByIdAndUpdate(userId, { $set: { refreshTokens: [] } }); // Clear cache await redis.del(\`user:\${userId}\`); } async verifyEmail(token: string) { const user = await User.findOne({ verificationToken: token }); if (!user) { throw new Error('Invalid verification token'); } user.isEmailVerified = true; user.verificationToken = undefined; await user.save(); return user.toJSON(); } async forgotPassword(email: string) { const user = await User.findOne({ email }); if (!user) { // Don't reveal if user exists return nanoid(32); } // Generate reset token const resetToken = nanoid(32); user.resetToken = resetToken; user.resetTokenExpiry = new Date(Date.now() + 3600000); // 1 hour await user.save(); return resetToken; } async resetPassword(token: string, newPassword: string) { const user = await User.findOne({ resetToken: token, resetTokenExpiry: { $gt: Date.now() } }); if (!user) { throw new Error('Invalid or expired reset token'); } user.password = newPassword; user.resetToken = undefined; user.resetTokenExpiry = undefined; user.refreshTokens = []; // Invalidate all refresh tokens await user.save(); return user.toJSON(); } private generateAccessToken(user: IUser): string { return jwt.sign( { id: user.id, email: user.email, role: user.role }, process.env.JWT_SECRET!, { expiresIn: '15m' } ); } private generateRefreshToken(user: IUser): string { return jwt.sign( { id: user.id }, process.env.JWT_REFRESH_SECRET!, { expiresIn: '7d' } ); } }`, // User service 'src/services/user.service.ts': `import { User } from '../models/user.model.js'; import { cache } from '../config/redis.js'; export class UserService { async getAllUsers(options: { page: number; limit: number; search?: string }) { const { page, limit, search } = options; const skip = (page - 1) * limit; const query: any = {}; if (search) { query.$or = [ { name: { $regex: search, $options: 'i' } }, { email: { $regex: search, $options: 'i' } } ]; } const [users, total] = await Promise.all([ User.find(query).skip(skip).limit(limit).sort('-createdAt'), User.countDocuments(query) ]); return { users: users.map(u => u.toJSON()), pagination: { page, limit, total, pages: Math.ceil(total / limit) } }; } async getUserById(id: string) { // Check cache first const cached = await cache.get<any>(\`user:\${id}\`); if (cached) return cached; const user = await User.findById(id); if (!user) { throw new Error('User not found'); } const userData = user.toJSON(); // Cache for 1 hour await cache.set(\`user:\${id}\`, userData, 3600); return userData; } async updateUser(id: string, updates: any) { const user = await User.findByIdAndUpdate( id, { $set: updates }, { new: true, runValidators: true } ); if (!user) { throw new Error('User not found'); } // Invalidate cache await cache.del(\`user:\${id}\`); return user.toJSON(); } async deleteUser(id: string) { const user = await User.findByIdAndDelete(id); if (!user) { throw new Error('User not found'); } // Invalidate cache await cache.del(\`user:\${id}\`); } async changePassword(userId: string, currentPassword: string, newPassword: string) { const user = await User.findById(userId); if (!user) { throw new Error('User not found'); } const isPasswordValid = await user.comparePassword(currentPassword); if (!isPasswordValid) { throw new Error('Current password is incorrect'); } user.password = newPassword; user.refreshTokens = []; // Invalidate all refresh tokens await user.save(); // Invalidate cache await cache.del(\`user:\${userId}\`); } async updateAvatar(userId: string, file: any) { // In production, upload to cloud storage (S3, Cloudinary, etc.) const avatarUrl = \`/uploads/avatars/\${file.filename}\`; await this.updateUser(userId, { avatar: avatarUrl }); return avatarUrl; } }`, // Todo service 'src/services/todo.service.ts': `import { Todo } from '../models/todo.model.js'; import { cache } from '../config/redis.js'; export class TodoService { async getAllTodos(options: { userId: string; page: number; limit: number; status?: string; priority?: string; }) { const { userId, page, limit, status, priority } = options; const skip = (page - 1) * limit; const query: any = { userId }; if (status) query.status = status; if (priority) query.priority = priority; const [todos, total] = await Promise.all([ Todo.find(query).skip(skip).limit(limit).sort('-createdAt'), Todo.countDocuments(query) ]); return { todos, pagination: { page, limit, total, pages: Math.ceil(total / limit) } }; } async getTodoById(id: string, userId: string) { const todo = await Todo.findOne({ _id: id, userId }); if (!todo) { throw new Error('Todo not found'); } return todo; } async createTodo(data: any) { const todo = await Todo.create(data); // Invalidate user's todo list cache await cache.delPattern(\`todos:\${data.userId}:*\`); return todo; } async updateTodo(id: string, userId: string, updates: any) { const todo = await Todo.findOneAndUpdate( { _id: id, userId }, { $set: updates }, { new: true, runValidators: true } ); if (!todo) { throw new Error('Todo not found'); } // Invalidate cache await cache.delPattern(\`todos:\${userId}:*\`); return todo; } async deleteTodo(id: string, userId: string) { const todo = await Todo.findOneAndDelete({ _id: id, userId }); if (!todo) { throw new Error('Todo not found'); } // Invalidate cache await cache.delPattern(\`todos:\${userId}:*\`); } async bulkDelete(ids: string[], userId: string) { const result = await Todo.deleteMany({ _id: { $in: ids }, userId }); // Invalidate cache await cache.delPattern(\`todos:\${userId}:*\`); return result.deletedCount; } async bulkUpdate(ids: string[], userId: string, updates: any) { const result = await Todo.updateMany( { _id: { $in: ids }, userId }, { $set: updates } ); // Invalidate cache await cache.delPattern(\`todos:\${userId}:*\`); return result.modifiedCount; } }`, // Email service (stub) 'src/services/email.service.ts': `import { logger } from '../utils/logger.js'; export class EmailService { async sendVerificationEmail(email: string, token: string) { // In production, integrate with email service (SendGrid, AWS SES, etc.) logger.info(\`Sending verification email to \${email} with token: \${token}\`); // Example with nodemailer: // const verificationUrl = \`\${process.env.APP_URL}/verify-email?token=\${token}\`; // await this.sendEmail({ // to: email, // subject: 'Verify your email', // html: \`Click <a href="\${verificationUrl}">here</a> to verify your email.\` // }); } async sendPasswordResetEmail(email: string, token: string) { logger.info(\`Sending password reset email to \${email} with token: \${token}\`); // const resetUrl = \`\${process.env.APP_URL}/reset-password?token=\${token}\`; // await this.sendEmail({ // to: email, // subject: 'Reset your password', // html: \`Click <a href="\${resetUrl}">here</a> to reset your password.\` // }); } private async sendEmail(options: { to: string; subject: string; html: string }) { // Implement email sending logic logger.info(\`Email would be sent to: \${options.to}\`); } }`, // Logger utility 'src/utils/logger.ts': `import pino from 'pino'; const isDevelopment = process.env.NODE_ENV === 'development'; export const logger = pino({ level: process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'info'), transport: isDevelopment ? { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname' } } : undefined, serializers: { error: pino.stdSerializers.err } });`, // Async handler utility 'src/utils/asyncHandler.ts': `import { Request, Response, NextHandler } from '@tinyhttp/app'; type AsyncRequestHandler = ( req: Request, res: Response, next: NextHandler ) => Promise<any>; export const asyncHandler = (fn: AsyncRequestHandler) => { return (req: Request, res: Response, next: NextHandler) => { Promise.resolve(fn(req, res, next)).catch(next); }; };`, // Upload utility 'src/utils/upload.ts': `import multer from 'multer'; import { nanoid } from 'nanoid'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, path.join(__dirname, '../../uploads')); }, filename: (req, file, cb) => { const uniqueSuffix = nanoid(); cb(null, \`\${uniqueSuffix}-\${file.originalname}\`); } }); const fileFilter = (req: any, file: any, cb: any) => { // Accept images only if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Only image files are allowed'), false); } }; export const upload = multer({ storage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 // 5MB } }); export const uploadSingle = (fieldName: string) => upload.single(fieldName); export const uploadMultiple = (fieldName: string, maxCount: number) => upload.array(fieldName, maxCount);`, // Environment variables '.env.example': `# Application NODE_ENV=development PORT=3000 APP_URL=http://localhost:3000 # Database MONGODB_URI=mongodb://localhost:27017/{{projectName}} # Redis REDIS_URL=redis://localhost:6379 # JWT JWT_SECRET=your-super-secret-jwt-key JWT_REFRESH_SECRET=you