Full-Stack Architecture and Design
Full-stack architecture encompasses both frontend and backend design. This article covers architectural patterns and design principles.
Introduction
Full-stack architecture provides:
- System design patterns
- Scalability strategies
- Performance optimization
- Security considerations
- Maintainability
Understanding architecture helps you:
- Design scalable systems
- Make architectural decisions
- Optimize performance
- Ensure security
- Build maintainable applications
Architectural Patterns
Monolithic Architecture
// โ
Good: Monolithic structure
// my-app/
// โโโ public/
// โโโ src/
// โ โโโ components/
// โ โโโ pages/
// โ โโโ services/
// โ โโโ App.js
// โโโ server/
// โ โโโ routes/
// โ โโโ controllers/
// โ โโโ models/
// โ โโโ app.js
// โโโ package.json
// โโโ README.md
// Advantages:
// - Simple to develop
// - Easy to deploy
// - Straightforward testing
// Disadvantages:
// - Difficult to scale
// - Technology lock-in
// - Slower development cycles
Microservices Architecture
// โ
Good: Microservices structure
// services/
// โโโ user-service/
// โ โโโ src/
// โ โโโ Dockerfile
// โ โโโ package.json
// โโโ post-service/
// โ โโโ src/
// โ โโโ Dockerfile
// โ โโโ package.json
// โโโ auth-service/
// โ โโโ src/
// โ โโโ Dockerfile
// โ โโโ package.json
// โโโ api-gateway/
// โโโ src/
// โโโ Dockerfile
// โโโ package.json
// Advantages:
// - Independent scaling
// - Technology flexibility
// - Faster deployment
// Disadvantages:
// - Increased complexity
// - Network latency
// - Data consistency challenges
Layered Architecture
// โ
Good: Layered architecture
// src/
// โโโ presentation/
// โ โโโ components/
// โ โโโ pages/
// โ โโโ App.js
// โโโ business/
// โ โโโ services/
// โ โโโ logic/
// โโโ persistence/
// โ โโโ models/
// โ โโโ repositories/
// โ โโโ database/
// โโโ infrastructure/
// โโโ config/
// โโโ middleware/
// โโโ utils/
// Advantages:
// - Clear separation of concerns
// - Easy to test
// - Maintainable
// Disadvantages:
// - Performance overhead
// - Tight coupling possible
// - Scalability limitations
System Design
Database Design
// โ
Good: Database schema design
// Users table
const userSchema = {
id: 'UUID',
email: 'String (unique)',
password: 'String (hashed)',
name: 'String',
role: 'Enum (user, admin)',
createdAt: 'DateTime',
updatedAt: 'DateTime'
};
// Posts table
const postSchema = {
id: 'UUID',
userId: 'UUID (FK)',
title: 'String',
content: 'Text',
published: 'Boolean',
createdAt: 'DateTime',
updatedAt: 'DateTime'
};
// Comments table
const commentSchema = {
id: 'UUID',
postId: 'UUID (FK)',
userId: 'UUID (FK)',
content: 'Text',
createdAt: 'DateTime',
updatedAt: 'DateTime'
};
// โ
Good: Indexing strategy
// CREATE INDEX idx_users_email ON users(email);
// CREATE INDEX idx_posts_userId ON posts(userId);
// CREATE INDEX idx_comments_postId ON comments(postId);
API Gateway Pattern
// โ
Good: API Gateway implementation
const express = require('express');
const httpProxy = require('express-http-proxy');
const app = express();
// Route to user service
app.use('/api/users', httpProxy('http://user-service:3001'));
// Route to post service
app.use('/api/posts', httpProxy('http://post-service:3002'));
// Route to auth service
app.use('/api/auth', httpProxy('http://auth-service:3003'));
// โ
Good: Request/response transformation
app.use('/api/users', (req, res, next) => {
// Add authentication
req.headers['x-user-id'] = req.user?.id;
next();
}, httpProxy('http://user-service:3001'));
// โ
Good: Rate limiting at gateway
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api/', limiter);
Scalability Patterns
Caching Strategy
// โ
Good: Multi-level caching
const redis = require('redis');
const client = redis.createClient();
// Cache layers:
// 1. Browser cache (HTTP headers)
// 2. CDN cache (static assets)
// 3. Application cache (Redis)
// 4. Database cache (query results)
// โ
Good: Cache invalidation
async function updateUser(userId, updates) {
const user = await User.findByIdAndUpdate(userId, updates);
// Invalidate cache
await client.del(`user:${userId}`);
await client.del('users:list');
return user;
}
// โ
Good: Cache-aside pattern
async function getUser(userId) {
const cached = await client.get(`user:${userId}`);
if (cached) {
return JSON.parse(cached);
}
const user = await User.findById(userId);
await client.setex(`user:${userId}`, 3600, JSON.stringify(user));
return user;
}
Database Replication
// โ
Good: Master-slave replication
// Master (write operations)
const masterDB = mongoose.connect('mongodb://master:27017/myapp');
// Slave (read operations)
const slaveDB = mongoose.connect('mongodb://slave:27017/myapp');
// โ
Good: Read/write splitting
async function getUser(userId) {
// Read from slave
return slaveDB.collection('users').findOne({ _id: userId });
}
async function updateUser(userId, updates) {
// Write to master
return masterDB.collection('users').updateOne(
{ _id: userId },
{ $set: updates }
);
}
Load Balancing
// โ
Good: Round-robin load balancing
const servers = [
'http://app1:3000',
'http://app2:3000',
'http://app3:3000'
];
let currentIndex = 0;
function getNextServer() {
const server = servers[currentIndex];
currentIndex = (currentIndex + 1) % servers.length;
return server;
}
// โ
Good: Sticky sessions
app.use(session({
store: new RedisStore(),
cookie: { secure: true }
}));
Security Architecture
Defense in Depth
// โ
Good: Multiple security layers
// 1. Network layer (firewall, WAF)
// 2. Application layer (authentication, authorization)
// 3. Data layer (encryption, access control)
// 4. Infrastructure layer (SSL/TLS, VPN)
// โ
Good: Input validation
const { body, validationResult } = require('express-validator');
app.post('/api/users', [
body('email').isEmail(),
body('password').isLength({ min: 8 }),
body('name').trim().notEmpty()
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process request
});
// โ
Good: Output encoding
app.get('/api/posts/:id', (req, res) => {
const post = getPost(req.params.id);
// Encode output to prevent XSS
res.json({
title: escapeHtml(post.title),
content: escapeHtml(post.content)
});
});
Performance Optimization
Frontend Optimization
// โ
Good: Code splitting
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Admin = lazy(() => import('./pages/Admin'));
// โ
Good: Image optimization
<picture>
<source srcSet="image.webp" type="image/webp" />
<img src="image.jpg" alt="description" loading="lazy" />
</picture>
// โ
Good: Bundle optimization
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors'
}
}
}
}
};
Backend Optimization
// โ
Good: Query optimization
// Bad: N+1 query problem
const users = await User.find();
for (const user of users) {
user.posts = await Post.find({ userId: user._id });
}
// Good: Use populate
const users = await User.find().populate('posts');
// โ
Good: Connection pooling
const pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
// โ
Good: Pagination
app.get('/api/posts', (req, res) => {
const page = req.query.page || 1;
const limit = req.query.limit || 10;
const offset = (page - 1) * limit;
Post.find().skip(offset).limit(limit);
});
Best Practices
-
Separate concerns:
// โ Good: Separation of concerns // Controllers handle HTTP // Services handle business logic // Repositories handle data access // โ Bad: Mixed concerns app.get('/users', async (req, res) => { const users = await db.query('SELECT * FROM users'); res.json(users); }); -
Use dependency injection:
// โ Good: Dependency injection class UserService { constructor(userRepository) { this.userRepository = userRepository; } } // โ Bad: Hard dependencies class UserService { constructor() { this.userRepository = new UserRepository(); } } -
Document architecture:
// โ Good: Architecture documentation // README.md with system design // Architecture diagrams // API documentation // โ Bad: No documentation
Summary
Full-stack architecture is essential. Key takeaways:
- Choose appropriate architectural patterns
- Design scalable systems
- Implement caching strategies
- Use load balancing
- Apply defense in depth
- Optimize performance
- Document architecture
- Plan for growth
Related Resources
Next Steps
- Learn about API Design
- Explore Database Design
- Study Authentication Flows
- Practice architecture design
- Build scalable systems
Comments