Introduction to Backend Development
Backend development forms the foundation of modern web applications, handling server-side logic, databases, APIs, and system architecture. A well-designed backend ensures scalability, security, and performance while providing reliable services to frontend applications and mobile clients.
"The backend is like the foundation of a house - invisible to users but crucial for everything else to work properly."
Backend Architecture Patterns
Monolithic Architecture
- Single deployable unit
- Shared database and resources
- Simpler development and testing
- Scaling challenges as app grows
Microservices Architecture
- Distributed services
- Independent deployment
- Technology diversity
- Better scalability and fault isolation
Serverless Architecture
- Function-as-a-Service (FaaS)
- Event-driven execution
- Automatic scaling
- Pay-per-execution model
RESTful API Design
REST Principles
REST API Best Practices
- Stateless: Each request contains all necessary information
- Resource-based: URLs represent resources, not actions
- HTTP Methods: Use appropriate verbs (GET, POST, PUT, DELETE)
- Consistent: Follow naming conventions and patterns
- Versioned: Support multiple API versions
API Endpoint Design
# Good REST API Design # Resources and Collections GET /api/v1/users # Get all users GET /api/v1/users/123 # Get specific user POST /api/v1/users # Create new user PUT /api/v1/users/123 # Update user (full) PATCH /api/v1/users/123 # Update user (partial) DELETE /api/v1/users/123 # Delete user # Nested Resources GET /api/v1/users/123/posts # Get user's posts POST /api/v1/users/123/posts # Create post for user # Query Parameters for Filtering GET /api/v1/users?page=2&limit=10&sort=created_at&order=desc GET /api/v1/posts?category=tech&status=published&author=john # Status Codes 200 OK # Successful GET, PUT, PATCH 201 Created # Successful POST 204 No Content # Successful DELETE 400 Bad Request # Invalid request data 401 Unauthorized # Authentication required 403 Forbidden # Access denied 404 Not Found # Resource doesn't exist 422 Unprocessable Entity # Validation errors 500 Internal Server Error # Server error
Node.js Backend Development
Express.js API Server
Express.js Setup
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { body, validationResult } = require('express-validator');
const app = express();
// Middleware
app.use(helmet()); // Security headers
app.use(cors()); // CORS support
app.use(express.json({ limit: '10mb' })); // JSON parsing
app.use(express.urlencoded({ extended: true }));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);
// Error handling middleware
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
if (err.name === 'ValidationError') {
return res.status(400).json({
success: false,
message: 'Validation Error',
errors: err.errors
});
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({
success: false,
message: 'Unauthorized access'
});
}
res.status(500).json({
success: false,
message: 'Internal server error'
});
};
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/users', require('./routes/users'));
app.use('/api/posts', require('./routes/posts'));
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Database Integration with Mongoose
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
// User Schema
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Username is required'],
unique: true,
trim: true,
minlength: [3, 'Username must be at least 3 characters'],
maxlength: [30, 'Username cannot exceed 30 characters']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
validate: {
validator: function(email) {
return /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(email);
},
message: 'Please enter a valid email'
}
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [6, 'Password must be at least 6 characters'],
select: false // Don't include in queries by default
},
profile: {
firstName: String,
lastName: String,
avatar: String,
bio: String
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
},
isActive: {
type: Boolean,
default: true
},
lastLogin: Date
}, {
timestamps: true
});
// Index for performance
userSchema.index({ email: 1 });
userSchema.index({ username: 1 });
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(12);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Compare password method
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
// Generate JWT token
userSchema.methods.generateAuthToken = function() {
const payload = {
id: this._id,
username: this.username,
role: this.role
};
return jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE || '24h'
});
};
// Virtual for full name
userSchema.virtual('fullName').get(function() {
return `${this.profile.firstName} ${this.profile.lastName}`.trim();
});
module.exports = mongoose.model('User', userSchema);
Authentication and Authorization
const jwt = require('jsonwebtoken');
const User = require('../models/User');
// Authentication middleware
const authenticate = async (req, res, next) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.id);
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'Invalid token or user not found'
});
}
req.user = user;
next();
} catch (error) {
res.status(401).json({
success: false,
message: 'Invalid token'
});
}
};
// Authorization middleware
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: 'Access denied. Insufficient permissions'
});
}
next();
};
};
// Auth routes
const express = require('express');
const router = express.Router();
// Register
router.post('/register', [
body('username').isLength({ min: 3 }).trim(),
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 6 })
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Validation errors',
errors: errors.array()
});
}
const { username, email, password } = req.body;
// Check if user exists
const existingUser = await User.findOne({
$or: [{ email }, { username }]
});
if (existingUser) {
return res.status(400).json({
success: false,
message: 'User already exists'
});
}
// Create user
const user = new User({ username, email, password });
await user.save();
const token = user.generateAuthToken();
res.status(201).json({
success: true,
message: 'User registered successfully',
token,
user: {
id: user._id,
username: user.username,
email: user.email,
role: user.role
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Registration failed',
error: error.message
});
}
});
// Login
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email }).select('+password');
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Update last login
user.lastLogin = new Date();
await user.save();
const token = user.generateAuthToken();
res.json({
success: true,
message: 'Login successful',
token,
user: {
id: user._id,
username: user.username,
email: user.email,
role: user.role
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Login failed',
error: error.message
});
}
});
module.exports = router;
Python Backend with FastAPI
FastAPI Application Setup
FastAPI Project Structure
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, EmailStr
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
import os
app = FastAPI(
title="My API",
description="A comprehensive API built with FastAPI",
version="1.0.0"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Database setup
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db")
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# JWT settings
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
security = HTTPBearer()
# User model
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(bind=engine)
# Pydantic models
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserResponse(BaseModel):
id: int
username: str
email: str
is_active: bool
created_at: datetime
class Config:
from_attributes = True
class Token(BaseModel):
access_token: str
token_type: str
# Dependency to get database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Utility functions
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
token = credentials.credentials
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = db.query(User).filter(User.username == username).first()
if user is None:
raise credentials_exception
return user
# Routes
@app.post("/register", response_model=UserResponse)
async def register_user(user: UserCreate, db: Session = Depends(get_db)):
# Check if user exists
db_user = db.query(User).filter(
(User.email == user.email) | (User.username == user.username)
).first()
if db_user:
raise HTTPException(
status_code=400,
detail="Username or email already registered"
)
# Create new user
hashed_password = get_password_hash(user.password)
db_user = User(
username=user.username,
email=user.email,
hashed_password=hashed_password
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@app.post("/login", response_model=Token)
async def login_user(email: str, password: str, db: Session = Depends(get_db)):
user = db.query(User).filter(User.email == email).first()
if not user or not verify_password(password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me", response_model=UserResponse)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
GraphQL APIs
GraphQL with Apollo Server
const { ApolloServer, gql } = require('apollo-server-express');
const { GraphQLScalarType } = require('graphql');
const { Kind } = require('graphql/language');
// Type definitions
const typeDefs = gql`
scalar DateTime
type User {
id: ID!
username: String!
email: String!
profile: Profile
posts: [Post!]!
createdAt: DateTime!
}
type Profile {
firstName: String
lastName: String
bio: String
avatar: String
}
type Post {
id: ID!
title: String!
content: String!
author: User!
published: Boolean!
createdAt: DateTime!
updatedAt: DateTime!
}
input CreatePostInput {
title: String!
content: String!
published: Boolean = false
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
myPosts: [Post!]!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
}
type Subscription {
postAdded: Post!
postUpdated: Post!
}
`;
// Custom scalar for DateTime
const dateTimeScalar = new GraphQLScalarType({
name: 'DateTime',
description: 'DateTime custom scalar type',
serialize(value) {
return value.toISOString();
},
parseValue(value) {
return new Date(value);
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
return null;
},
});
// Resolvers
const resolvers = {
DateTime: dateTimeScalar,
Query: {
users: async () => {
return await User.find().populate('profile');
},
user: async (_, { id }) => {
return await User.findById(id).populate('profile');
},
posts: async () => {
return await Post.find().populate('author');
},
post: async (_, { id }) => {
return await Post.findById(id).populate('author');
},
myPosts: async (_, __, { user }) => {
if (!user) throw new Error('Authentication required');
return await Post.find({ author: user.id });
}
},
Mutation: {
createPost: async (_, { input }, { user }) => {
if (!user) throw new Error('Authentication required');
const post = new Post({
...input,
author: user.id
});
await post.save();
await post.populate('author');
// Publish subscription
pubsub.publish('POST_ADDED', { postAdded: post });
return post;
},
updatePost: async (_, { id, input }, { user }) => {
if (!user) throw new Error('Authentication required');
const post = await Post.findById(id);
if (!post) throw new Error('Post not found');
if (post.author.toString() !== user.id) {
throw new Error('Not authorized');
}
Object.assign(post, input);
post.updatedAt = new Date();
await post.save();
await post.populate('author');
pubsub.publish('POST_UPDATED', { postUpdated: post });
return post;
},
deletePost: async (_, { id }, { user }) => {
if (!user) throw new Error('Authentication required');
const post = await Post.findById(id);
if (!post) throw new Error('Post not found');
if (post.author.toString() !== user.id) {
throw new Error('Not authorized');
}
await Post.findByIdAndDelete(id);
return true;
}
},
Subscription: {
postAdded: {
subscribe: () => pubsub.asyncIterator(['POST_ADDED'])
},
postUpdated: {
subscribe: () => pubsub.asyncIterator(['POST_UPDATED'])
}
},
User: {
posts: async (parent) => {
return await Post.find({ author: parent.id });
}
},
Post: {
author: async (parent) => {
return await User.findById(parent.author);
}
}
};
Microservices Architecture
Service Decomposition
- Decompose by business capability
- Single responsibility per service
- Loose coupling between services
- Database per service pattern
Inter-Service Communication
- Synchronous: HTTP/REST, gRPC
- Asynchronous: Message queues, Events
- Service mesh for complex routing
- Circuit breaker pattern
Service Discovery
- Service registry (Consul, Eureka)
- Load balancing strategies
- Health checks and monitoring
- API gateway pattern
Message Queue with Redis
const Queue = require('bull');
const redis = require('redis');
// Create Redis connection
const redisClient = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
});
// Create queues
const emailQueue = new Queue('email processing', {
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
}
});
const imageQueue = new Queue('image processing', {
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
}
});
// Email processing job
emailQueue.process('send-email', async (job) => {
const { to, subject, html, attachments } = job.data;
try {
// Send email using nodemailer or other service
await sendEmail({ to, subject, html, attachments });
console.log(`Email sent to ${to}`);
return { success: true, recipient: to };
} catch (error) {
console.error('Email sending failed:', error);
throw error;
}
});
// Image processing job
imageQueue.process('resize-image', 5, async (job) => {
const { imagePath, sizes } = job.data;
try {
const results = [];
for (const size of sizes) {
const resizedPath = await resizeImage(imagePath, size);
results.push({
size,
path: resizedPath
});
// Update progress
const progress = (results.length / sizes.length) * 100;
job.progress(progress);
}
return results;
} catch (error) {
console.error('Image processing failed:', error);
throw error;
}
});
// Add jobs to queues
const addEmailJob = async (emailData) => {
const job = await emailQueue.add('send-email', emailData, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
});
return job.id;
};
const addImageProcessingJob = async (imageData) => {
const job = await imageQueue.add('resize-image', imageData, {
attempts: 2,
removeOnComplete: 10,
removeOnFail: 5
});
return job.id;
};
// Queue event handlers
emailQueue.on('completed', (job, result) => {
console.log(`Email job ${job.id} completed:`, result);
});
emailQueue.on('failed', (job, err) => {
console.log(`Email job ${job.id} failed:`, err.message);
});
imageQueue.on('progress', (job, progress) => {
console.log(`Image job ${job.id} progress: ${progress}%`);
});
module.exports = {
emailQueue,
imageQueue,
addEmailJob,
addImageProcessingJob
};
API Documentation and Testing
API Documentation with Swagger
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'My API',
version: '1.0.0',
description: 'A comprehensive API documentation',
},
servers: [
{
url: 'http://localhost:3000/api',
description: 'Development server',
},
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
},
apis: ['./routes/*.js'], // paths to files containing OpenAPI definitions
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
/**
* @swagger
* /users:
* get:
* summary: Get all users
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: Page number
* - in: query
* name: limit
* schema:
* type: integer
* description: Number of items per page
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* $ref: '#/components/schemas/User'
* pagination:
* $ref: '#/components/schemas/Pagination'
*/
API Testing with Jest
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
describe('User API', () => {
let authToken;
let userId;
beforeAll(async () => {
// Setup test database
await User.deleteMany({});
});
afterAll(async () => {
// Cleanup
await User.deleteMany({});
});
describe('POST /api/auth/register', () => {
test('should register a new user', async () => {
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/auth/register')
.send(userData);
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.user.email).toBe(userData.email);
expect(response.body.token).toBeDefined();
authToken = response.body.token;
userId = response.body.user.id;
});
test('should not register user with existing email', async () => {
const userData = {
username: 'testuser2',
email: 'test@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/auth/register')
.send(userData);
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});
});
describe('POST /api/auth/login', () => {
test('should login with valid credentials', async () => {
const loginData = {
email: 'test@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/auth/login')
.send(loginData);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.token).toBeDefined();
});
test('should not login with invalid credentials', async () => {
const loginData = {
email: 'test@example.com',
password: 'wrongpassword'
};
const response = await request(app)
.post('/api/auth/login')
.send(loginData);
expect(response.status).toBe(401);
expect(response.body.success).toBe(false);
});
});
describe('GET /api/users/me', () => {
test('should get current user with valid token', async () => {
const response = await request(app)
.get('/api/users/me')
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.user.id).toBe(userId);
});
test('should not get user without token', async () => {
const response = await request(app)
.get('/api/users/me');
expect(response.status).toBe(401);
});
});
});
Performance and Scalability
Caching Strategies
- Redis for application-level caching
- CDN for static content delivery
- Database query result caching
- HTTP response caching headers
Load Balancing
- Round-robin distribution
- Least connections algorithm
- Geographic load balancing
- Health check monitoring
Database Optimization
- Index optimization
- Connection pooling
- Read replicas for scaling reads
- Database sharding strategies
Monitoring and Logging
Application Monitoring
const winston = require('winston');
const morgan = require('morgan');
// Configure winston logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// HTTP request logging
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim())
}
}));
// Performance monitoring middleware
const performanceMonitor = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('Request performance', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
userAgent: req.get('User-Agent'),
ip: req.ip
});
// Alert on slow requests
if (duration > 1000) {
logger.warn('Slow request detected', {
method: req.method,
url: req.url,
duration: `${duration}ms`
});
}
});
next();
};
app.use(performanceMonitor);
module.exports = logger;
Deployment and DevOps
Docker Configuration
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Change ownership
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=mongodb://mongo:27017/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- mongo
- redis
restart: unless-stopped
mongo:
image: mongo:5
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
volumes:
mongo_data:
Conclusion
Backend development requires understanding multiple concepts from API design to system architecture. Focus on building secure, scalable, and maintainable systems. Start with simple applications and gradually incorporate advanced patterns like microservices, caching, and monitoring.
Practice building different types of APIs, experiment with various databases, and always consider security and performance from the beginning. The backend is the foundation of great applications!