Chapter 01
Node.js and TypeScript essentials
How Node.js runs your code, how TypeScript describes it and how packages and frameworks fit together. Everything later in the sheet builds on these.
The event loop
- What
- Node.js runs your JavaScript on one thread. The event loop picks up callbacks when I/O, timers or promises are ready, while libuv does the actual network and file work in the background.
- Why
- It lets one process serve thousands of concurrent connections cheaply, as long as each callback finishes quickly. Promise callbacks (microtasks) always run before the next timer.
console.log('1 sync');
setTimeout(() => console.log('4 timer'), 0);
Promise.resolve().then(() => console.log('3 microtask'));
console.log('2 sync');Watch out
CPU-heavy work (big JSON parsing, image resizing, sync crypto) blocks every request; move it to worker_threads or a separate job.
Promises and async/await
Also called Promise, async, await
- What
- A Promise represents a value that arrives later.
asyncfunctions return a Promise, andawaitpauses the function until it settles, without blocking the thread. - Why
- Almost every backend call (database, HTTP, Redis) is async.
Promise.allruns independent calls concurrently instead of one after another.
const [user, orders] = await Promise.all([
userRepo.findById(id),
orderRepo.forUser(id),
]);Watch out
Forgetting await (the error escapes your try/catch) or awaiting inside a loop when the calls could run together.
TypeScript types and interfaces
Also called TS, TypeScript
- What
- TypeScript adds static types to JavaScript: interfaces and type aliases describe object shapes, unions list allowed values, and the compiler checks every use. Enable
strictmode. - Why
- Types catch wrong field names, missing
nullchecks and bad refactors before the code runs, and make a large codebase navigable.
type Role = 'admin' | 'member';
interface User {
id: string;
email: string;
role: Role;
deletedAt?: Date;
}Watch out
Types vanish at runtime: a request body typed as User is still unchecked JSON until you validate it.
npm, package.json and lockfiles
Also called npm, package-lock.json, pnpm
- What
package.jsonlists dependencies and scripts; the lockfile pins the exact version of every package in the tree. npm, pnpm and Yarn all read them.- Why
- The lockfile makes installs reproducible, so CI and production run the same code you tested.
npm ciinstalls exactly what the lockfile says.
npm install express zod
npm install -D typescript vitest @types/express
npm ci # clean, lockfile-exact install for CI
npm run build
npm auditWatch out
Not committing the lockfile, or running npm install in CI, so a new minor version sneaks into production.
NestJS controllers, providers and modules
Also called NestJS, Nest
- What
- NestJS is a structured framework on top of Express or Fastify. Controllers handle routes, providers (services) hold logic, and modules group them; Nest injects dependencies through constructors.
- Why
- It gives large TypeScript codebases one consistent layout, with built-in pipes for validation, guards for auth and easy testing through injected fakes.
@Controller('users')
export class UsersController {
constructor(private readonly users: UsersService) {}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
const user = await this.users.findOne(id);
if (!user) throw new NotFoundException();
return user;
}
}Watch out
Forgetting to list a provider in its module, which fails at startup with "Nest cannot resolve dependencies".
JavaScript core: values, scope and closures
Also called closures, let and const, references, JavaScript
- What
- Primitives (string, number, boolean, null, undefined) are copied, while objects are passed by reference.
letandconstare block scoped, and a closure is a function that keeps access to the variables where it was defined. - Why
- Backend bugs often come from shared object references,
==coercion or a closure capturing the wrong value, so these basics come before Express or NestJS.
const user = { name: 'Ali' };
const same = user; // same object, not a copy
same.name = 'Sara'; // user.name is now 'Sara'
const clone = { ...user }; // shallow copy
function counter() {
let n = 0;
return () => ++n; // the closure keeps n alive
}Watch out
Using == instead of ===, so '0' == 0 is true.