logo
Back to Blog
SaaSArchitectureScalabilityNode.jsDatabase

Building Scalable SaaS Architecture: Lessons from the Trenches

Learn the key architectural decisions that can make or break your SaaS application as it scales from 100 to 100,000+ users.

NL
Nikola Lalovic
January 15, 2024
8 min read

Building Scalable SaaS Architecture: Lessons from the Trenches

After building multiple SaaS applications that have scaled from zero to thousands of users, I've learned that the architectural decisions you make early on can either accelerate your growth or become major bottlenecks later.

## The Foundation: Database Design

Your database is the heart of your SaaS application. Here are the key principles I follow:

### Multi-tenancy Strategy

There are three main approaches to multi-tenancy:

1. **Shared Database, Shared Schema** - All tenants share the same database and tables
2. **Shared Database, Separate Schema** - Each tenant has their own schema
3. **Separate Database** - Each tenant has their own database

For most SaaS applications, I recommend starting with approach #1 and migrating larger customers to #3 as needed.

### Indexing Strategy

Proper indexing is crucial for performance:

sql
-- Always include tenant_id in your indexes
CREATE INDEX idx_orders_tenant_created ON orders(tenant_id, created_at);
CREATE INDEX idx_users_tenant_email ON users(tenant_id, email);


## API Design for Scale

### Rate Limiting

Implement rate limiting from day one:

javascript
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});

app.use('/api/', limiter);


### Pagination

Always paginate your API responses:

javascript
app.get('/api/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = Math.min(parseInt(req.query.limit) || 10, 100);
const offset = (page - 1) * limit;

const users = await User.findAndCountAll({
where: { tenant_id: req.user.tenant_id },
limit,
offset,
order: [['created_at', 'DESC']]
});

res.json({
data: users.rows,
pagination: {
page,
limit,
total: users.count,
pages: Math.ceil(users.count / limit)
}
});
});


## Caching Strategy

### Redis for Session Management

javascript
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const client = redis.createClient();

app.use(session({
store: new RedisStore({ client: client }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: false, maxAge: 86400000 } // 24 hours
}));


### Application-level Caching

``javascript
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes

app.get('/api/dashboard-stats', async (req, res) => {
const cacheKey =
dashboard-${req.user.tenant_id};
let stats = cache.get(cacheKey);

if (!stats) {
stats = await calculateDashboardStats(req.user.tenant_id);
cache.set(cacheKey, stats);
}

res.json(stats);
});


## Monitoring and Observability

### Health Checks

javascript
app.get('/health', async (req, res) => {
try {
// Check database connection
await sequelize.authenticate();

// Check Redis connection
await client.ping();

res.json({ status: 'healthy', timestamp: new Date().toISOString() });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: error.message });
}
});


### Error Tracking

Use tools like Sentry for error tracking:

javascript
const Sentry = require('@sentry/node');

Sentry.init({ dsn: process.env.SENTRY_DSN });

app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());


## Deployment and Infrastructure

### Docker Configuration

dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

USER node

CMD ["npm", "start"]


### Environment Variables

Never hardcode configuration:

javascript
const config = {
port: process.env.PORT || 3000,
database: {
host: process.env.DB_HOST,
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD
},
redis: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT || 6379
}
};


## Security Considerations

### Input Validation

javascript
const Joi = require('joi');

const userSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
name: Joi.string().min(2).max(50).required()
});

app.post('/api/users', async (req, res) => {
const { error, value } = userSchema.validate(req.body);

if (error) {
return res.status(400).json({ error: error.details[0].message });
}

// Process validated data
});


### SQL Injection Prevention

Always use parameterized queries:

javascript
// Bad
const query =
SELECT * FROM users WHERE email = '${email}';

// Good
const query = 'SELECT * FROM users WHERE email = $1';
const result = await client.query(query, [email]);


## Performance Optimization

### Database Query Optimization

javascript
// Use includes to avoid N+1 queries
const users = await User.findAll({
include: [{
model: Profile,
attributes: ['avatar', 'bio']
}],
where: { tenant_id: tenantId }
});


### Frontend Optimization

javascript
// Implement virtual scrolling for large lists
import { FixedSizeList as List } from 'react-window';

const VirtualizedList = ({ items }) => (
height={600}
itemCount={items.length}
itemSize={50}
itemData={items}
>
{({ index, style, data }) => (

{data[index].name}

)}

);
``

## Conclusion

Building scalable SaaS architecture is about making smart decisions early and planning for growth. Focus on:

1. **Database design** - Get your data model right from the start
2. **API design** - Build APIs that can handle growth
3. **Caching** - Implement caching at multiple levels
4. **Monitoring** - Know what's happening in your application
5. **Security** - Build security in from day one

Remember, premature optimization is the root of all evil, but planning for scale is essential. Start simple, measure everything, and optimize based on real data.

---

*Have questions about SaaS architecture? Feel free to reach out or join our developer community to discuss!*

NL

Nikola Lalovic

Full Stack Developer

Passionate full-stack developer and tech consultant helping businesses build scalable solutions and accelerate growth.

Related Articles

Full StackReact

Modern Full Stack Development in 2024: The Complete Guide

A comprehensive guide to the modern full-stack development landscape, including the best tools, frameworks, and practices for 2024.

Read More

Found This Helpful?

If you enjoyed this article and want to discuss your project or get personalized advice, I'd love to hear from you.