Skip to main content
โšก Calmops

Full-Stack Architecture and Design

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

  1. 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);
    });
    
  2. Use dependency injection:

    // โœ… Good: Dependency injection
    class UserService {
      constructor(userRepository) {
        this.userRepository = userRepository;
      }
    }
    
    // โŒ Bad: Hard dependencies
    class UserService {
      constructor() {
        this.userRepository = new UserRepository();
      }
    }
    
  3. 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

Next Steps

Comments