Backend Development Prompt Templates
AI prompt templates for backend development. Build APIs, services, and server-side logic.
Overview
Backend development powers the server-side logic, data processing, and API endpoints that frontend applications rely on. These prompts help you build solid APIs, implement business logic, handle authentication, and manage data persistence. The key is specifying your framework, data models, and the business rules that drive behavior.
Best Practices
Specify your framework and language version (Express.js, FastAPI, Django, etc.) for compatible code
Include your data models or database schema so the code works with your actual data
Describe the business logic and validation rules that need to be enforced
Mention authentication and authorization requirements for proper security handling
Include error handling expectations and what errors should be returned to clients
Prompt Templates
1. REST API Endpoint
Create a REST API endpoint with these specifications: **Framework:** [EXPRESS / FASTAPI / DJANGO / RAILS / etc.] **Endpoint:** [METHOD] [PATH] **Purpose:** [WHAT IT DOES] **Request:** - Authentication: [REQUIRED / OPTIONAL / NONE] - Body/Params: [EXPECTED INPUT] - Validation: [VALIDATION RULES] **Response:** - Success: [STATUS CODE AND BODY] - Errors: [ERROR SCENARIOS] **Business logic:** - [RULE 1] - [RULE 2] **Database operations:** - [WHAT DATA IS READ/WRITTEN] **Provide:** 1. Route handler/controller 2. Input validation 3. Service layer logic 4. Error handling 5. Response formatting
Framework: Express.js with TypeScript
Endpoint: POST /api/v1/bookings
Purpose: Create a booking for a service appointment
Auth: JWT required
Body: { serviceId, date, time, notes }
Validation: Date must be in future, time must be available slot
Business logic: Check availability, create booking, send confirmation email
Database: Check existing bookings, insert new booking```typescript
// routes/bookings.ts
import { Router } from 'express';
import { body, validationResult } from 'express-validator';
import { authenticateJWT } from '../middleware/auth';
import { BookingService } from '../services/BookingService';
import { BadRequestError, ConflictError } from '../errors';
const router = Router();
const bookingService = new BookingService();
// Validation middleware
const createBookingValidation = [
body('serviceId')
.isUUID()
.withMessage('Invalid service ID'),
body('date')
.isISO8601()
.withMessage('Invalid date format')
.custom((value) => {
const date = new Date(value);
if (date <= new Date()) {
throw new Error('Date must be in the future');
}
return true;
}),
body('time')
.matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
.withMessage('Invalid time format (HH:MM)'),
body('notes')
.optional()
.isLength({ max: 500 })
.withMessage('Notes must be 500 characters or less'),
];
/**
* POST /api/v1/bookings
* Create a new service booking
*/
router.post(
'/',
authenticateJWT,
createBookingValidation,
async (req, res, next) => {
try {
// Check validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
throw new BadRequestError('Validation failed', errors.array());
}
const { serviceId, date, time, notes } = req.body;
const userId = req.user!.id;
// Create booking through service layer
const booking = await bookingService.createBooking({
userId,
serviceId,
date: new Date(date),
time,
notes,
});
// Return created booking
res.status(201).json({
success: true,
data: {
id: booking.id,
service: {
id: booking.service.id,
name: booking.service.name,
},
date: booking.date.toISOString().split('T')[0],
time: booking.time,
status: booking.status,
confirmationNumber: booking.confirmationNumber,
createdAt: booking.createdAt,
},
});
} catch (error) {
next(error);
}
}
);
export default router;
```
```typescript
// services/BookingService.ts
import { prisma } from '../lib/prisma';
import { EmailService } from './EmailService';
import { ConflictError, NotFoundError } from '../errors';
import { generateConfirmationNumber } from '../utils/booking';
interface CreateBookingInput {
userId: string;
serviceId: string;
date: Date;
time: string;
notes?: string;
}
export class BookingService {
private emailService = new EmailService();
async createBooking(input: CreateBookingInput) {
const { userId, serviceId, date, time, notes } = input;
// 1. Verify service exists
const service = await prisma.service.findUnique({
where: { id: serviceId },
});
if (!service) {
throw new NotFoundError('Service not found');
}
// 2. Check availability
const dateStr = date.toISOString().split('T')[0];
const existingBooking = await prisma.booking.findFirst({
where: {
serviceId,
date: {
gte: new Date(`${dateStr}T00:00:00Z`),
lt: new Date(`${dateStr}T23:59:59Z`),
},
time,
status: { in: ['pending', 'confirmed'] },
},
});
if (existingBooking) {
throw new ConflictError('This time slot is no longer available');
}
// 3. Create booking
const booking = await prisma.booking.create({
data: {
userId,
serviceId,
date,
time,
notes,
status: 'pending',
confirmationNumber: generateConfirmationNumber(),
},
include: {
service: true,
user: true,
},
});
// 4. Send confirmation email (async, don't block response)
this.emailService.sendBookingConfirmation(booking).catch((error) => {
console.error('Failed to send confirmation email:', error);
// Log for retry queue, don't fail the booking
});
return booking;
}
async checkAvailability(serviceId: string, date: Date): Promise<string[]> {
const dateStr = date.toISOString().split('T')[0];
// Get all booked slots for this service on this date
const bookedSlots = await prisma.booking.findMany({
where: {
serviceId,
date: {
gte: new Date(`${dateStr}T00:00:00Z`),
lt: new Date(`${dateStr}T23:59:59Z`),
},
status: { in: ['pending', 'confirmed'] },
},
select: { time: true },
});
const bookedTimes = new Set(bookedSlots.map((b) => b.time));
// Return available slots (9 AM - 5 PM, hourly)
const allSlots = [
'09:00', '10:00', '11:00', '12:00',
'13:00', '14:00', '15:00', '16:00', '17:00',
];
return allSlots.filter((slot) => !bookedTimes.has(slot));
}
}
```
```typescript
// errors/index.ts
export class AppError extends Error {
constructor(
message: string,
public statusCode: number,
public code: string
) {
super(message);
this.name = this.constructor.name;
}
}
export class BadRequestError extends AppError {
constructor(message: string, public details?: any) {
super(message, 400, 'BAD_REQUEST');
}
}
export class NotFoundError extends AppError {
constructor(message: string) {
super(message, 404, 'NOT_FOUND');
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, 409, 'CONFLICT');
}
}
```When implementing new API endpoints, CRUD operations, or any server-side request handling
- •Separate validation, business logic, and data access into distinct layers for testability
- •Send emails and other side effects asynchronously, don't block the API response
2. Database Service Layer
Create a service layer for database operations: **Entity:** [WHAT YOU'RE MANAGING] **ORM/Database:** [PRISMA / TYPEORM / SEQUELIZE / RAW SQL] **Operations needed:** - [OPERATION 1 - e.g. CRUD] - [OPERATION 2 - e.g. search/filter] - [OPERATION 3 - e.g. aggregations] **Relationships:** - [RELATED ENTITIES] **Business rules:** - [RULE 1 - e.g. soft delete] - [RULE 2 - e.g. audit logging] **Performance considerations:** - [CACHING NEEDS] - [QUERY OPTIMIZATION] **Provide:** 1. Service class with all operations 2. TypeScript interfaces 3. Error handling 4. Query optimization
Entity: Product for e-commerce ORM: Prisma Operations: CRUD, search with filters, inventory management, featured products Relationships: Category, Reviews, Inventory Business rules: Soft delete, track price history, audit changes Performance: Cache popular products, optimize search queries
```typescript
// services/ProductService.ts
import { Prisma, Product, ProductStatus } from '@prisma/client';
import { prisma } from '../lib/prisma';
import { cache } from '../lib/cache';
import { NotFoundError, BadRequestError } from '../errors';
// Types
export interface CreateProductInput {
name: string;
description: string;
price: number;
categoryId: string;
sku: string;
inventory: number;
images: string[];
}
export interface UpdateProductInput {
name?: string;
description?: string;
price?: number;
categoryId?: string;
images?: string[];
status?: ProductStatus;
}
export interface ProductSearchOptions {
query?: string;
categoryId?: string;
minPrice?: number;
maxPrice?: number;
inStock?: boolean;
status?: ProductStatus;
sortBy?: 'price' | 'name' | 'createdAt' | 'popularity';
sortOrder?: 'asc' | 'desc';
page?: number;
limit?: number;
}
export interface PaginatedProducts {
products: Product[];
total: number;
page: number;
totalPages: number;
hasMore: boolean;
}
export class ProductService {
private readonly CACHE_TTL = 300; // 5 minutes
private readonly CACHE_PREFIX = 'product:';
// ==================== CREATE ====================
async createProduct(input: CreateProductInput, userId: string): Promise<Product> {
// Check for duplicate SKU
const existing = await prisma.product.findUnique({
where: { sku: input.sku },
});
if (existing) {
throw new BadRequestError(`Product with SKU ${input.sku} already exists`);
}
// Verify category exists
const category = await prisma.category.findUnique({
where: { id: input.categoryId },
});
if (!category) {
throw new NotFoundError('Category not found');
}
// Create product with initial inventory and price history
const product = await prisma.$transaction(async (tx) => {
const product = await tx.product.create({
data: {
name: input.name,
description: input.description,
price: input.price,
sku: input.sku,
categoryId: input.categoryId,
images: input.images,
status: 'draft',
},
});
// Create initial inventory record
await tx.inventory.create({
data: {
productId: product.id,
quantity: input.inventory,
reserved: 0,
},
});
// Create initial price history
await tx.priceHistory.create({
data: {
productId: product.id,
price: input.price,
changedBy: userId,
},
});
// Audit log
await tx.auditLog.create({
data: {
entityType: 'product',
entityId: product.id,
action: 'create',
userId,
newValues: input as any,
},
});
return product;
});
return product;
}
// ==================== READ ====================
async getProductById(id: string): Promise<Product> {
// Try cache first
const cached = await cache.get(`${this.CACHE_PREFIX}${id}`);
if (cached) {
return JSON.parse(cached);
}
const product = await prisma.product.findUnique({
where: { id, deletedAt: null },
include: {
category: true,
inventory: true,
_count: { select: { reviews: true } },
},
});
if (!product) {
throw new NotFoundError('Product not found');
}
// Cache the result
await cache.setex(
`${this.CACHE_PREFIX}${id}`,
this.CACHE_TTL,
JSON.stringify(product)
);
return product;
}
async searchProducts(options: ProductSearchOptions): Promise<PaginatedProducts> {
const {
query,
categoryId,
minPrice,
maxPrice,
inStock,
status = 'active',
sortBy = 'createdAt',
sortOrder = 'desc',
page = 1,
limit = 20,
} = options;
// Build where clause
const where: Prisma.ProductWhereInput = {
deletedAt: null,
status,
};
if (query) {
where.OR = [
{ name: { contains: query, mode: 'insensitive' } },
{ description: { contains: query, mode: 'insensitive' } },
{ sku: { contains: query, mode: 'insensitive' } },
];
}
if (categoryId) {
where.categoryId = categoryId;
}
if (minPrice !== undefined || maxPrice !== undefined) {
where.price = {};
if (minPrice !== undefined) where.price.gte = minPrice;
if (maxPrice !== undefined) where.price.lte = maxPrice;
}
if (inStock) {
where.inventory = { quantity: { gt: 0 } };
}
// Build orderBy
const orderBy: Prisma.ProductOrderByWithRelationInput =
sortBy === 'popularity'
? { reviews: { _count: sortOrder } }
: { [sortBy]: sortOrder };
// Execute query with count
const [products, total] = await Promise.all([
prisma.product.findMany({
where,
orderBy,
skip: (page - 1) * limit,
take: limit,
include: {
category: { select: { id: true, name: true } },
inventory: { select: { quantity: true } },
},
}),
prisma.product.count({ where }),
]);
const totalPages = Math.ceil(total / limit);
return {
products,
total,
page,
totalPages,
hasMore: page < totalPages,
};
}
// ==================== UPDATE ====================
async updateProduct(
id: string,
input: UpdateProductInput,
userId: string
): Promise<Product> {
const existing = await prisma.product.findUnique({
where: { id, deletedAt: null },
});
if (!existing) {
throw new NotFoundError('Product not found');
}
const product = await prisma.$transaction(async (tx) => {
// If price changed, record in price history
if (input.price !== undefined && input.price !== existing.price) {
await tx.priceHistory.create({
data: {
productId: id,
price: input.price,
previousPrice: existing.price,
changedBy: userId,
},
});
}
// Update product
const updated = await tx.product.update({
where: { id },
data: {
...input,
updatedAt: new Date(),
},
});
// Audit log
await tx.auditLog.create({
data: {
entityType: 'product',
entityId: id,
action: 'update',
userId,
previousValues: existing as any,
newValues: input as any,
},
});
return updated;
});
// Invalidate cache
await cache.del(`${this.CACHE_PREFIX}${id}`);
return product;
}
// ==================== DELETE (Soft) ====================
async deleteProduct(id: string, userId: string): Promise<void> {
const existing = await prisma.product.findUnique({
where: { id, deletedAt: null },
});
if (!existing) {
throw new NotFoundError('Product not found');
}
await prisma.$transaction(async (tx) => {
// Soft delete
await tx.product.update({
where: { id },
data: {
deletedAt: new Date(),
status: 'archived',
},
});
// Audit log
await tx.auditLog.create({
data: {
entityType: 'product',
entityId: id,
action: 'delete',
userId,
previousValues: existing as any,
},
});
});
// Invalidate cache
await cache.del(`${this.CACHE_PREFIX}${id}`);
}
// ==================== INVENTORY ====================
async updateInventory(
productId: string,
quantity: number,
userId: string
): Promise<void> {
await prisma.$transaction(async (tx) => {
const inventory = await tx.inventory.findUnique({
where: { productId },
});
if (!inventory) {
throw new NotFoundError('Product inventory not found');
}
await tx.inventory.update({
where: { productId },
data: { quantity },
});
await tx.auditLog.create({
data: {
entityType: 'inventory',
entityId: productId,
action: 'update',
userId,
previousValues: { quantity: inventory.quantity },
newValues: { quantity },
},
});
});
// Invalidate product cache
await cache.del(`${this.CACHE_PREFIX}${productId}`);
}
// ==================== FEATURED ====================
async getFeaturedProducts(limit = 8): Promise<Product[]> {
const cacheKey = `${this.CACHE_PREFIX}featured:${limit}`;
const cached = await cache.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const products = await prisma.product.findMany({
where: {
status: 'active',
deletedAt: null,
featured: true,
inventory: { quantity: { gt: 0 } },
},
take: limit,
orderBy: { featuredOrder: 'asc' },
include: {
category: { select: { id: true, name: true } },
},
});
await cache.setex(cacheKey, this.CACHE_TTL, JSON.stringify(products));
return products;
}
}
```When building data access layers, implementing business logic, or creating reusable database operations
- •Use transactions for operations that modify multiple tables, maintains data integrity
- •Invalidate cache on writes, not just set TTL, stale data causes subtle bugs
3. Authentication Middleware
Create authentication middleware: **Framework:** [EXPRESS / FASTAPI / etc.] **Auth method:** [JWT / SESSION / API KEY / OAUTH] **Requirements:** - Token validation: [HOW TO VALIDATE] - User lookup: [HOW TO GET USER] - Role-based access: [IF NEEDED] - Token refresh: [IF NEEDED] **Error handling:** - [WHAT HAPPENS ON INVALID TOKEN] - [WHAT HAPPENS ON EXPIRED TOKEN] **Provide:** 1. Auth middleware 2. Role checking middleware 3. Token utilities 4. Error responses
Framework: Express.js with TypeScript Auth method: JWT (access + refresh tokens) Requirements: Validate JWT, load user from DB, check roles, handle expired tokens Error handling: Return 401 for invalid, include refresh suggestion for expired
```typescript
// middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt, { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
import { prisma } from '../lib/prisma';
import { UnauthorizedError, ForbiddenError } from '../errors';
// Extend Express Request type
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
role: string;
};
}
}
}
interface JWTPayload {
userId: string;
email: string;
role: string;
type: 'access' | 'refresh';
}
const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
/**
* Middleware to authenticate JWT tokens
*/
export function authenticateJWT(
req: Request,
res: Response,
next: NextFunction
) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedError('No token provided');
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, JWT_SECRET) as JWTPayload;
// Ensure it's an access token, not a refresh token
if (payload.type !== 'access') {
throw new UnauthorizedError('Invalid token type');
}
req.user = {
id: payload.userId,
email: payload.email,
role: payload.role,
};
next();
} catch (error) {
if (error instanceof TokenExpiredError) {
throw new UnauthorizedError(
'Token expired',
'TOKEN_EXPIRED'
);
}
if (error instanceof JsonWebTokenError) {
throw new UnauthorizedError('Invalid token');
}
throw error;
}
}
/**
* Optional authentication - continues if no token, validates if present
*/
export function optionalAuth(
req: Request,
res: Response,
next: NextFunction
) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return next(); // Continue without user
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, JWT_SECRET) as JWTPayload;
if (payload.type === 'access') {
req.user = {
id: payload.userId,
email: payload.email,
role: payload.role,
};
}
} catch {
// Invalid token - continue without user
}
next();
}
/**
* Role-based access control middleware
*/
export function requireRole(...allowedRoles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
throw new UnauthorizedError('Authentication required');
}
if (!allowedRoles.includes(req.user.role)) {
throw new ForbiddenError(
`This action requires one of these roles: ${allowedRoles.join(', ')}`
);
}
next();
};
}
/**
* Middleware to verify user still exists and is active
*/
export async function verifyUser(
req: Request,
res: Response,
next: NextFunction
) {
if (!req.user) {
return next();
}
const user = await prisma.user.findUnique({
where: { id: req.user.id },
select: { id: true, status: true, role: true },
});
if (!user || user.status !== 'active') {
throw new UnauthorizedError('User account is not active');
}
// Update role if it changed
req.user.role = user.role;
next();
}
```
```typescript
// utils/tokens.ts
import jwt from 'jsonwebtoken';
import { prisma } from '../lib/prisma';
import crypto from 'crypto';
const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
interface User {
id: string;
email: string;
role: string;
}
export function generateAccessToken(user: User): string {
return jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role,
type: 'access',
},
JWT_SECRET,
{ expiresIn: ACCESS_TOKEN_EXPIRY }
);
}
export function generateRefreshToken(user: User): string {
return jwt.sign(
{
userId: user.id,
type: 'refresh',
},
JWT_REFRESH_SECRET,
{ expiresIn: REFRESH_TOKEN_EXPIRY }
);
}
export async function generateTokenPair(user: User): Promise<{
accessToken: string;
refreshToken: string;
expiresIn: number;
}> {
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
// Store refresh token hash in database (for revocation)
const tokenHash = crypto
.createHash('sha256')
.update(refreshToken)
.digest('hex');
await prisma.refreshToken.create({
data: {
tokenHash,
userId: user.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
});
return {
accessToken,
refreshToken,
expiresIn: 900, // 15 minutes in seconds
};
}
export async function refreshAccessToken(refreshToken: string): Promise<{
accessToken: string;
expiresIn: number;
}> {
// Verify refresh token
const payload = jwt.verify(refreshToken, JWT_REFRESH_SECRET) as {
userId: string;
type: string;
};
if (payload.type !== 'refresh') {
throw new Error('Invalid token type');
}
// Check if token exists in database (not revoked)
const tokenHash = crypto
.createHash('sha256')
.update(refreshToken)
.digest('hex');
const storedToken = await prisma.refreshToken.findUnique({
where: { tokenHash },
include: { user: true },
});
if (!storedToken || storedToken.revokedAt) {
throw new Error('Token has been revoked');
}
if (storedToken.expiresAt < new Date()) {
throw new Error('Token has expired');
}
// Generate new access token
const accessToken = generateAccessToken(storedToken.user);
return {
accessToken,
expiresIn: 900,
};
}
export async function revokeRefreshToken(refreshToken: string): Promise<void> {
const tokenHash = crypto
.createHash('sha256')
.update(refreshToken)
.digest('hex');
await prisma.refreshToken.update({
where: { tokenHash },
data: { revokedAt: new Date() },
});
}
export async function revokeAllUserTokens(userId: string): Promise<void> {
await prisma.refreshToken.updateMany({
where: { userId, revokedAt: null },
data: { revokedAt: new Date() },
});
}
```
### Usage Example
```typescript
import { Router } from 'express';
import { authenticateJWT, requireRole, optionalAuth } from '../middleware/auth';
const router = Router();
// Public route
router.get('/products', productController.list);
// Authenticated route
router.post('/orders', authenticateJWT, orderController.create);
// Admin only route
router.delete('/users/:id', authenticateJWT, requireRole('admin'), userController.delete);
// Optional auth (different behavior for logged in users)
router.get('/feed', optionalAuth, feedController.getFeed);
```When implementing authentication, authorization, or protecting API routes
- •Store refresh token hashes, not raw tokens, if the database is compromised, tokens can't be stolen
- •Always differentiate token types (access vs refresh) to prevent misuse
4. Background Job Handler
Create a background job processor: **Job type:** [EMAIL / REPORT / SYNC / CLEANUP / etc.] **Queue system:** [BULL / BEE-QUEUE / AGENDA / CELERY] **Job details:** - Input data: [WHAT THE JOB RECEIVES] - Processing logic: [WHAT IT DOES] - Output/side effects: [WHAT IT PRODUCES] **Requirements:** - Retry strategy: [HOW TO HANDLE FAILURES] - Concurrency: [HOW MANY PARALLEL] - Scheduling: [IF RECURRING] - Monitoring: [LOGGING / METRICS] **Provide:** 1. Job definition 2. Worker/processor 3. Job scheduling 4. Error handling and retries 5. Monitoring hooks
Job type: Send email notifications Queue: Bull (Redis-based) Input: userId, emailType, templateData Processing: Render template, send via SendGrid Requirements: Retry 3 times with exponential backoff, 5 concurrent jobs, log all attempts
```typescript
// jobs/emailJob.ts
import { Queue, Worker, Job } from 'bullmq';
import { Redis } from 'ioredis';
import { SendGridService } from '../services/SendGridService';
import { prisma } from '../lib/prisma';
import { renderEmailTemplate } from '../utils/email';
import { metrics } from '../lib/metrics';
// Job data interface
interface EmailJobData {
userId: string;
emailType: 'welcome' | 'password_reset' | 'order_confirmation' | 'newsletter';
templateData: Record<string, any>;
priority?: number;
}
// Redis connection
const connection = new Redis(process.env.REDIS_URL!, {
maxRetriesPerRequest: null,
});
// Create queue
export const emailQueue = new Queue<EmailJobData>('email', {
connection,
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000, // 2s, 4s, 8s
},
removeOnComplete: {
age: 24 * 60 * 60, // Keep completed jobs for 24 hours
count: 1000, // Keep last 1000 completed jobs
},
removeOnFail: {
age: 7 * 24 * 60 * 60, // Keep failed jobs for 7 days
},
},
});
// Worker processor
const sendGridService = new SendGridService();
export const emailWorker = new Worker<EmailJobData>(
'email',
async (job: Job<EmailJobData>) => {
const { userId, emailType, templateData } = job.data;
const startTime = Date.now();
job.log(`Processing email job: ${emailType} for user ${userId}`);
try {
// 1. Get user email
const user = await prisma.user.findUnique({
where: { id: userId },
select: { email: true, name: true, emailPreferences: true },
});
if (!user) {
throw new Error(`User ${userId} not found`);
}
// 2. Check email preferences (respect unsubscribe)
if (emailType === 'newsletter' && !user.emailPreferences?.newsletter) {
job.log('User has unsubscribed from newsletters, skipping');
return { skipped: true, reason: 'unsubscribed' };
}
// 3. Render email template
const { subject, html, text } = await renderEmailTemplate(emailType, {
...templateData,
userName: user.name,
});
// 4. Send email
const result = await sendGridService.send({
to: user.email,
subject,
html,
text,
});
// 5. Log success
await prisma.emailLog.create({
data: {
userId,
emailType,
status: 'sent',
messageId: result.messageId,
sentAt: new Date(),
},
});
job.log(`Email sent successfully: ${result.messageId}`);
// 6. Record metrics
metrics.increment('email.sent', { type: emailType });
metrics.timing('email.duration', Date.now() - startTime, { type: emailType });
return { success: true, messageId: result.messageId };
} catch (error) {
// Log failure
await prisma.emailLog.create({
data: {
userId,
emailType,
status: 'failed',
error: error instanceof Error ? error.message : 'Unknown error',
attemptNumber: job.attemptsMade + 1,
},
});
metrics.increment('email.failed', { type: emailType });
throw error; // Re-throw to trigger retry
}
},
{
connection,
concurrency: 5,
limiter: {
max: 100,
duration: 60000, // 100 jobs per minute (SendGrid rate limit)
},
}
);
// Event handlers for monitoring
emailWorker.on('completed', (job, result) => {
console.log(`Email job ${job.id} completed`, result);
});
emailWorker.on('failed', (job, error) => {
console.error(`Email job ${job?.id} failed:`, error.message);
// Alert if final attempt failed
if (job && job.attemptsMade >= 3) {
// Send alert to monitoring system
metrics.increment('email.permanent_failure', { type: job.data.emailType });
}
});
emailWorker.on('error', (error) => {
console.error('Email worker error:', error);
});
```
```typescript
// services/emailService.ts - High-level API for queueing emails
import { emailQueue, EmailJobData } from '../jobs/emailJob';
export class EmailService {
/**
* Queue a welcome email for a new user
*/
async sendWelcomeEmail(userId: string, data: { verificationLink: string }) {
return this.queueEmail(userId, 'welcome', data, { priority: 1 });
}
/**
* Queue a password reset email
*/
async sendPasswordResetEmail(userId: string, data: { resetLink: string }) {
return this.queueEmail(userId, 'password_reset', data, { priority: 1 });
}
/**
* Queue an order confirmation email
*/
async sendOrderConfirmationEmail(
userId: string,
data: { orderId: string; orderTotal: number; items: any[] }
) {
return this.queueEmail(userId, 'order_confirmation', data);
}
/**
* Queue newsletter to multiple users
*/
async sendNewsletterBatch(userIds: string[], data: { subject: string; content: string }) {
const jobs = userIds.map((userId) => ({
name: 'newsletter',
data: {
userId,
emailType: 'newsletter' as const,
templateData: data,
},
opts: { priority: 10 }, // Low priority
}));
return emailQueue.addBulk(jobs);
}
/**
* Internal method to queue emails
*/
private async queueEmail(
userId: string,
emailType: EmailJobData['emailType'],
templateData: Record<string, any>,
options: { priority?: number; delay?: number } = {}
) {
const jobId = `${emailType}-${userId}-${Date.now()}`;
return emailQueue.add(
emailType,
{ userId, emailType, templateData },
{
jobId,
priority: options.priority,
delay: options.delay,
}
);
}
}
```
### Usage
```typescript
// In your controller/service
const emailService = new EmailService();
// After user registration
await emailService.sendWelcomeEmail(user.id, {
verificationLink: `https://example.com/verify?token=${token}`,
});
// After order completion
await emailService.sendOrderConfirmationEmail(order.userId, {
orderId: order.id,
orderTotal: order.total,
items: order.items,
});
```
### Monitoring Dashboard Data
```typescript
// Get queue stats
async function getQueueStats() {
const [waiting, active, completed, failed] = await Promise.all([
emailQueue.getWaitingCount(),
emailQueue.getActiveCount(),
emailQueue.getCompletedCount(),
emailQueue.getFailedCount(),
]);
return { waiting, active, completed, failed };
}
```When implementing async processing, scheduled tasks, or any operation that shouldn't block the main request
- •Use exponential backoff for retries, constant retry intervals can overwhelm failing services
- •Set job retention policies, unbounded job history fills up Redis
Common Mistakes to Avoid
Not validating input at the API boundary, trusting that callers send valid data
Mixing business logic with route handlers, makes code hard to test and reuse
Forgetting to handle database transaction rollbacks when operations fail
Frequently Asked Questions
Backend development powers the server-side logic, data processing, and API endpoints that frontend applications rely on. These prompts help you build solid APIs, implement business logic, handle authentication, and manage data persistence. The key is specifying your framework, data models, and the business rules that drive behavior.
Related Templates
Code Review Prompt Templates
AI prompt templates for thorough code reviews. Get comprehensive feedback on code quality, security, and best practices.
Debugging Prompt Templates
AI prompt templates for debugging code. Identify issues, understand errors, and find solutions faster.
Code Documentation Prompt Templates
AI prompt templates for writing code documentation. Create clear comments, READMEs, and API docs.
Have your own prompt to optimize?