Learning Backend for Real: NestJS, PostgreSQL, Redis, BullMQ & Elasticsearch
A backend developer roadmap for Node.js: what to learn in what order, from HTTP and NestJS to PostgreSQL, auth, Redis, BullMQ and Elasticsearch.
A backend developer roadmap is the order you learn things in, so that each new tool solves a problem you've already run into. For the Node.js stack this series uses, the order is: how servers and HTTP work, then NestJS, then PostgreSQL and an ORM, then authentication, then Redis for caching, BullMQ for background jobs, and Elasticsearch for search. Each step below links to a full post, says what to build, and tells you when you're ready to move on.
Every backend tutorial eventually hands you the same five names: NestJS, PostgreSQL, Redis, BullMQ, Elasticsearch. Learned in a random order, they feel like five unrelated chores. They're one system, and each tool exists because the others are bad at something. This page is the map: the steps first, then what each tool is for and what happens when a single request touches all five.
The roadmap at a glance
- How servers work: the client-server model (HTTP, requests, responses, status codes)
- Your first NestJS API with validation (routes, DTOs, ValidationPipe)
- NestJS fundamentals (dependency injection, modules, the request lifecycle)
- PostgreSQL and database essentials (tables, joins, indexes, transactions)
- Connecting NestJS to PostgreSQL with Sequelize (models, associations, migrations)
- Full-stack auth with NestJS and React (password hashing, sessions vs JWT, guards)
- Redis essentials (caching, TTL, rate limiting, pub/sub)
- BullMQ essentials (queues, workers, retries)
- Elasticsearch essentials (inverted index, analyzers, aggregations)
A left-to-right flow of nine steps: how servers work, first NestJS API, NestJS fundamentals, PostgreSQL, Sequelize ORM, authentication, Redis caching, BullMQ background jobs, and Elasticsearch search.
Steps 1 to 6 are the core: by the end you have an API with a real database and logins, which is most of what a backend job asks for day to day. Steps 7 to 9 are tools you add when a specific problem shows up, and the step-by-step section below says what that problem looks like.
The problem before the tools
Imagine you're building something simple, like a blog platform. You need to: store posts reliably, respond to requests fast, do slow work (like sending emails or resizing images) without making people wait, and let users search through thousands of posts instantly. No single tool does all of that well. So instead of one giant tool, backend systems are usually built from a handful of small, specialized ones, each excellent at one job. That's exactly what these five are.
NestJS — the framework that organizes everything
NestJS is a Node.js framework, built with TypeScript, that gives your backend code structure. Without a framework, a growing Node app tends to turn into a folder of tangled files where nobody remembers what calls what. NestJS forces a shape onto your code: Controllers handle incoming HTTP requests, Services hold your actual business logic, and Modules group related controllers and services together.
Behind the scenes, NestJS relies heavily on dependency injection. Instead of a service creating its own database connection, NestJS creates it once and hands it to any class that asks for it. That single idea is why NestJS apps stay testable and organized even as they grow into hundreds of files.
Deep dive: Building your first NestJS API, then NestJS fundamentals.
PostgreSQL — where your data actually lives
PostgreSQL is a relational database — the permanent, trustworthy home for your data. Users, posts, orders, comments: anything that must never quietly disappear belongs here. What makes PostgreSQL special is that it's ACID-compliant: atomic, consistent, isolated and durable. In practice, a transaction either fully happens or doesn't happen at all, and once it's committed it survives a server crash. If you transfer money between two accounts, you never end up with money deducted from one and never added to the other.
Under the hood, Postgres writes every change to a Write-Ahead Log before touching the actual data files. If the database crashes, it replays that log on restart to recover exactly where it left off. That's the quiet mechanism that lets you trust it with things you can't afford to lose.
Deep dive: PostgreSQL essentials, then connecting NestJS to it with Sequelize.
Redis — the shortcut so you don't hit the database every time
PostgreSQL is reliable, but every query does real work: parsing, planning, checking transactions, and sometimes reading from disk. Redis is an in-memory data store. Everything it holds lives in RAM and a lookup by key does very little work, so a read usually takes well under a millisecond, versus a few milliseconds for a typical database query. Every query it saves is also load taken off Postgres. The usual pattern: when a request comes in, NestJS checks Redis first. If the data is there (a cache hit), it returns right away without touching Postgres. If it's not there (a cache miss), NestJS fetches from Postgres, then stores a copy in Redis for next time.
Because Redis lives in memory, it can also do things a database shouldn't be asked to do quickly, like counting page views in real time, rate-limiting how many requests a user can make per minute, or storing session data.
This is where TTL (Time To Live) comes in. It's easy to forget, but it keeps the whole cache honest. When you write a key into Redis, you usually attach an expiry, like SET key value EX 60, telling Redis "keep this for 60 seconds, then delete it." Without a TTL, cached data can go stale forever: someone updates a post in Postgres, but Redis keeps serving the old version because nothing told it to expire. With a TTL, the cache refreshes itself. Once the key expires, the next request is a cache miss, Postgres is queried again, and a fresh value is cached in its place.
SET post:42 "<cached post data>" EX 60
# key disappears automatically after 60 secondsDeep dive: Redis essentials.
BullMQ — doing slow work without making people wait
Some tasks are too slow to run inside a request-response cycle. Sending a confirmation email, resizing an uploaded image, or indexing a new post for search can take seconds — nobody wants to stare at a loading spinner for that. BullMQ is a job queue built on top of Redis. Instead of doing the slow work immediately, NestJS adds a small job description to a queue and responds to the user right away. A separate worker process picks up jobs from that queue and processes them in the background, at its own pace.
This is why BullMQ needs Redis: the queue itself (the list of pending jobs, retries, and job status) is stored as Redis data structures. If a worker crashes mid-job, BullMQ can retry it, because the job is still sitting safely in Redis until it's marked complete.
await postQueue.add('index-post', { postId: post.id });
// worker picks this up in the background laterDeep dive: BullMQ essentials.
Elasticsearch — finding things fast, even in millions of records
PostgreSQL has built-in full-text search, and it's good enough for many apps. It runs out of road when you want typo-tolerant results, fine control over ranking, or fast search across millions of documents. Elasticsearch is a search engine designed for exactly that. Instead of scanning rows one by one, it builds an inverted index ahead of time: a map from every word to every document that contains it. A search for "redis caching" jumps straight to the matching documents instead of reading everything.
Elasticsearch isn't your source of truth, though — Postgres still is. Whenever a post is created or updated, it also gets copied into Elasticsearch's index, usually through a background job, so search results stay fresh without slowing down the original write.
Deep dive: Elasticsearch essentials.
How they fit together
A client sends HTTP requests to a NestJS API. NestJS reads and writes PostgreSQL, checks Redis first and stores rows there on a cache miss, and adds background jobs to a BullMQ queue kept in Redis. A worker reads the full record from PostgreSQL and indexes it in Elasticsearch, which NestJS queries for full-text search.
Five boxes, but one clear division of labor: NestJS is the traffic controller, PostgreSQL is the source of truth, Redis is the fast shortcut, BullMQ is the to-do list for slow work, and Elasticsearch is the search index. None of them replace each other — they cover each other's weaknesses.
Watching a real request move through the system
Diagrams are easier to trust once you trace one real action through them. Here's what happens, roughly step by step, when someone publishes a new post and another user searches for it.
Sequence: the user creates a post; NestJS inserts it into PostgreSQL, invalidates the cached post list in Redis, adds an index-post job to BullMQ and returns 201 Created. A worker later fetches the post from PostgreSQL and indexes it in Elasticsearch. A search request goes from NestJS to Elasticsearch, which returns ranked matches.
Notice that the user gets their 201 Created response before the post is indexed in Elasticsearch. The indexing happens a moment later, in the background, through the worker. That gap is usually a second or two, because Elasticsearch also waits for its next refresh (every second by default) before a new document shows up in search. The user never feels it. That's the point of BullMQ.
The backend developer roadmap, step by step
Each step has three parts: what to learn, one small thing to build, and a check for when you're ready to move on. The checks matter more than any time estimate. If you can't pass one yet, stay on that step a little longer.
Before step 1: what you need already
This series assumes you can write JavaScript, including async/await, and can read TypeScript classes and types. If TypeScript is new, the TypeScript handbook is enough to get going. You'll also want a terminal you're comfortable in, Git, a recent Node.js LTS (NestJS 12 needs v20.19+ or v22.12+), and Postman or curl to send requests. None of that is covered here.
Step 1: how servers and HTTP work
Learn what a request and a response are, the main HTTP methods, status codes, headers, JSON and ports. Read what is a server and the client-server model. Build: nothing yet; send a few requests to a public API with Postman and read the raw responses. Ready when: you can explain what happens between typing a URL and seeing JSON, and say what 201, 400, 401, 404 and 500 each mean.
Step 2: your first NestJS API
Learn routes, controllers, services, request bodies and validation. Read building your first NestJS API. Build: create, read, update and delete endpoints for one resource, like notes, kept in memory. Ready when: a request with a missing or wrongly typed field gets a 400 without you writing a single if-statement for it.
Step 3: NestJS fundamentals
Learn dependency injection, modules, and where middleware, guards, interceptors, pipes and filters run. Read NestJS fundamentals. Build: split the notes API into its own module, add an interceptor that logs how long each request takes, and write one test that swaps a service for a mock. Ready when: you can fix a "Nest can't resolve dependencies" error by reading it, without searching for it.
Step 4: PostgreSQL and SQL
Learn tables, primary and foreign keys, joins, indexes and transactions, in plain SQL first. Read PostgreSQL essentials, with the official PostgreSQL tutorial open beside it. Build: a users table and a notes table linked by a foreign key, then write the queries your API will need by hand in psql. Ready when: you can write a JOIN without looking it up and use EXPLAIN to show an index being used.
Step 5: connect NestJS to the database
Learn models, associations, migrations and transactions through an ORM. Read NestJS and Sequelize. Build: move the notes API from memory into Postgres. Ready when: restarting the server loses nothing, and every schema change goes through a migration instead of a manual ALTER TABLE.
Step 6: authentication
Learn password hashing, sessions vs JWTs, and guards. Read full-stack auth with NestJS and React. Build: signup and login, and make every notes route belong to the logged-in user. Ready when: a request without a valid session or token gets a 401, one user can't read another user's notes, and no password is ever stored in plain text. Skim the OWASP Top 10 here too; broken access control is the first item on it for a reason.
Step 7: Redis, when something is slow or repeated
Learn caching with a TTL, cache invalidation, and rate limiting. Read Redis essentials. Build: cache one slow endpoint, and rate-limit the login route. Ready when: you've measured a cache hit against a cache miss yourself, and an update in Postgres shows up in the API without waiting for the TTL to run out.
Step 8: BullMQ, when work is too slow for a request
Learn queues, workers, retries with backoff, and scheduled jobs. Read BullMQ essentials. Build: send the signup welcome email from a background job instead of inside the request. Ready when: the signup response comes back before the email is sent, and a job that fails is retried on its own.
Step 9: Elasticsearch, when search outgrows Postgres
Learn indexes, mappings, analyzers, the query DSL and aggregations. Read Elasticsearch essentials. Try PostgreSQL's built-in full-text search first; for a few thousand notes it may be all you need. Build: copy notes into Elasticsearch from the BullMQ worker whenever one is created or updated. Ready when: a search with a typo still finds the right note, and a new note shows up in search within a few seconds.
Adding all five at once is how this stack becomes overwhelming. Adding them one at a time, each to solve a problem you can actually feel, is how it becomes obvious.
What this roadmap leaves out
Being honest about the gaps: this series covers one stack, from first route to search. A full backend job also expects things it doesn't teach yet:
- –Testing beyond a single unit test: integration tests against a real database, and end-to-end tests of the HTTP API.
- –Shipping it: Docker, environment config, CI, and deploying somewhere real.
- –Running it: structured logs, metrics, tracing, and knowing when something breaks before your users tell you.
- –API design topics: pagination, versioning, idempotency, and alternatives like GraphQL.
- –Larger-scale design: message brokers like Kafka, read replicas, and splitting a service in two.
A good time to pick up testing and deployment is right after step 6, when you have an app with users and data that's worth shipping. The rest makes more sense once you've run something in production.
Common questions
Is NestJS a good way to learn backend development?
If you already know TypeScript, yes. It asks for more concepts up front than plain Express, but the biggest one, dependency injection, works much the same way in Spring and ASP.NET Core, so it transfers if you ever switch languages.
Should I learn SQL before using an ORM?
Yes, which is why PostgreSQL is step 4 and Sequelize is step 5. An ORM writes SQL for you, and when a query is slow or wrong, you debug it by reading the SQL it generated.
Do I need Redis, BullMQ and Elasticsearch for a small project?
Often not. A small app can run on NestJS and PostgreSQL alone. Add Redis when you've measured something slow, BullMQ when a request is waiting on slow work, and Elasticsearch when Postgres full-text search stops being good enough.
How long does it take to learn backend development?
It depends far more on how many hours you put in each week than on the stack, so any number would be a guess. A better measure is the ready-when check on each step. Passing all six core checks with your own project is a solid foundation.
I'm a frontend engineer who builds the UI on top of backends like this one. If you need that, see my work or get in touch.