Architecture of Small Web Applications
For small applications, predictability matters more than the number of technologies. A simple monolith with one database and a clear deployment path usually has fewer failure points and is easier to debug than premature microservices.
Related topics: nginx and Reverse Proxy, SQL and PostgreSQL, Docker, APIs and System Integrations and Software Testing.
1. Goal
A small project does not need microservices, Kubernetes and five databases.
Often this is enough:
Browser
↓
nginx
↓
backend
↓
PostgreSQL
2. Monolith
A monolith is one application containing most of the business logic.
Advantages:
- simple deployment,
- less infrastructure,
- simpler debugging,
- easier transactions.
For small and medium projects it is often the best choice.
3. Frontend
It may use:
- server-rendered HTML,
- static HTML/CSS/JS,
- a React/Vue SPA.
Do not choose an SPA automatically.
4. Backend
Responsible for logic, authentication, APIs, database access, integrations and validation.
5. Reverse proxy
nginx can handle TLS, domain routing, static files and proxying to the backend.
6. Database
PostgreSQL provides durable data, constraints, transactions and indexes.
7. Cache
Redis becomes useful when you have a concrete reason such as caching, sessions, queues or rate limiting.
Do not add it just because it feels expected.
8. Configuration
code → repository
environment → env / config
secrets → secret store / env
Keep these concerns separate.
9. Example Go structure
cmd/
internal/
http/
service/
repository/
config/
migrations/
web/
Dockerfile
README.md
10. Layers
A practical split:
handler
↓
service
↓
repository
↓
database
You do not need twelve layers.
11. Request flow
GET /orders/42
↓
nginx
↓
router
↓
handler
↓
service
↓
repository
↓
PostgreSQL
↓
JSON/HTML response
12. Background jobs
For long-running work:
request
↓
enqueue
↓
worker
But do not add a queue when a normal cron job is enough.
13. Cron / scheduler
Good for reports, synchronization, periodic checks and cleanup.
14. External APIs
Keep integration code separate:
internal/integrations/openrouter
internal/integrations/github
This makes testing and provider changes easier.
15. Error handling
Differentiate user errors, missing resources, conflicts and system failures.
Do not return HTTP 500 for everything.
16. Logging
Useful logs contain timestamp, level, event, request ID and relevant context.
17. Request ID
A request ID helps trace one operation across several layers.
request_id=abc123
18. Deployment
A simple approach:
GitHub
↓
CI
↓
Docker image
↓
VPS
↓
docker compose
↓
nginx
19. Backup
Back up the database, uploads and possibly configuration. Source code belongs in Git.
20. Scaling
Measure first.
Typical order:
- improve queries,
- add indexes,
- add cache where it helps,
- add resources,
- only then consider more complex architecture.
21. Microservices
They make sense when there are real reasons such as independent teams, separate scaling, independent deployment cycles or clear domain boundaries.
They are not an automatic “higher level”.
22. MVP
A good MVP can be one application, one database, simple deployment, monitoring and backups.
23. What you should know
You should be able to sketch request flow, choose a simple architecture, separate configuration from code, understand nginx/backend/database roles, avoid overengineering and plan deployment and backups.
Sources and further documentation
- The Twelve-Factor App: https://12factor.net/
- PostgreSQL documentation: https://www.postgresql.org/docs/current/
- nginx documentation: https://nginx.org/en/docs/