# Building a Production-Ready Node.js + TypeScript Backend

*A Deep, Code-First, Professional Guide*

This article is not a checklist.  
It’s a **guided walkthrough**, where we read code together and understand **why it exists**.

If you copy-paste without understanding you’ll forget it.  
If you understand *why* you’ll never need to memorize.

Let’s do this the right way.

---

## 1\. Git Setup — Why It Comes Before Code

```bash
git init
```

This single command creates a hidden `.git` directory.

### What’s Inside `.git`?

* Object database (your entire history)
    
* Branch pointers
    
* Commit references
    
* Hooks (we’ll use these later)
    

Once this exists, **your project has memory**.

---

## `.gitignore` — Preventing Silent Damage

Here’s an example of what a Node `.gitignore` looks like:

```plaintext
node_modules/
dist/
.env
logs/
```

### Why Each Line Matters

* `node_modules/`  
    Can be regenerated at any time → never commit
    
* `dist/`  
    Compiled output → source of truth is `src/`
    
* `.env`  
    Secrets → never leak credentials
    
* `logs/`  
    Runtime artifacts → environment-specific
    

This file protects your repository **from accidental disasters**.

---

## 2\. Node Version Manager — Solving a Real Production Problem

When you run:

```bash
nvm install 18
```

NVM:

1. Downloads Node 18 binaries
    
2. Stores them in `~/.nvm`
    
3. Updates your shell PATH
    

When you later run:

```bash
nvm use 18
```

Your terminal **switches binaries**, not installations.

---

### `.nvmrc` in Action

```text
18
```

Now this command:

```bash
nvm install
```

Reads `.nvmrc` and installs the correct version automatically.

**CI pipelines rely on this behavior.**

---

## 3\. Initializing the Project

```bash
npm init -y
```

Generates:

```json
{
  "name": "project-name",
  "version": "1.0.0",
  "scripts": {}
}
```

### Why `scripts` Is Important

Later, we’ll run:

```bash
npm run lint
npm run test
npm run build
```

`package.json` becomes the **control panel** of your project.

---

## 4\. TypeScript — Understanding What Actually Happens

### Installing TypeScript

```bash
npm i -D typescript
```

This installs the **TypeScript compiler**, not runtime code.

### Key Insight (Very Important)

> Node.js **never runs TypeScript**  
> Node.js runs **JavaScript only**

TypeScript exists **before runtime**.

---

### `tsconfig.json` Explained

After:

```bash
npx tsc --init
```

You configure:

```json
{
  "rootDir": "./src",
  "outDir": "./dist"
}
```

### What Happens Internally?

When you run:

```bash
npx tsc
```

TypeScript:

1. Reads `.ts` files from `src`
    
2. Type-checks everything
    
3. Emits `.js` files into `dist`
    
4. Stops if types are broken
    

Your server **always runs** `dist`, never `src`.

---

### Why `@types/node` Is Required

```bash
npm i -D @types/node
```

Without this:

```ts
process.env.PORT
```

TypeScript says:

> I don’t know what `process` is

Because Node is written in JavaScript, not TypeScript.  
`@types/node` teaches TypeScript **how Node works**.

---

## 5\. Prettier — Code Formatting as a Machine Rule

Install:

```bash
npm i -D prettier
```

Create config:

```json
{
  "singleQuote": false,
  "semi": true
}
```

### What Prettier Actually Does

Prettier:

* Parses your code into an AST
    
* Reprints it using fixed rules
    
* Ignores personal preferences
    

Example:

```ts
const x='hello'
```

Becomes:

```ts
const x = "hello";
```

No discussion. No arguments.

---

### Why Prettier Runs in CI

```bash
npx prettier . --check
```

This command:

* Does NOT change files
    
* Fails if formatting is wrong
    

Perfect for automation.

---

## 6\. ESLint — Understanding Code Behavior

Install:

```bash
npm i -D eslint @eslint/js typescript-eslint
```

### ESLint vs Prettier (Critical Difference)

| Tool | Purpose |
| --- | --- |
| Prettier | Formatting |
| ESLint | Logic & correctness |

Example ESLint catches:

```ts
async function test() {
  fetchData(); // missing await
}
```

Prettier **cannot** catch this. ESLint can.

---

## 7\. Git Hooks — Enforcing Quality Automatically

Install Husky:

```bash
npx husky init
```

Creates:

```text
.husky/
  pre-commit
```

### What `pre-commit` Does

Before Git creates a commit:

1. Husky intercepts
    
2. Runs your commands
    
3. Fails commit if checks fail
    

---

### Why `lint-staged` Is Necessary

Without it:

* ESLint runs on entire repo
    
* Commits become slow
    

With this config:

```json
"lint-staged": {
  "*.ts": [
    "npm run format:check",
    "npm run lint:check"
  ]
}
```

Only **changed files** are checked.

Fast. Clean. Scalable.

---

## 8\. Configuration Layer — Clean Architecture in Practice

### Bad Pattern (Avoid)

```ts
process.env.PORT
process.env.DB_URL
```

Scattered everywhere → impossible to refactor.

---

### Good Pattern (Your Code)

```ts
import dotenv from "dotenv";
dotenv.config();

const Config = {
  PORT: Number(process.env.PORT) || 3000,
  NODE_ENV: process.env.NODE_ENV ?? "development",
};

export default Config;
```

Now the rest of the app does:

```ts
Config.PORT
```

If tomorrow config comes from JSON or Vault — **no app code changes**.

---

## 9\. Express App vs Server — Explained with Code

### `app.ts`

```ts
import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.send("Hello World");
});

export default app;
```

This file:

* Defines routes
    
* Adds middleware
    
* Exports Express instance
    

No networking here.

---

### `server.ts`

```ts
import app from "./app.js";
import Config from "./config/index.js";

app.listen(Config.PORT, () => {
  console.log(`Server running on ${Config.PORT}`);
});
```

Now:

* App is reusable
    
* Tests can import app
    
* Server lifecycle is controlled
    

---

## 10\. Winston Logger — Real Logging Explained

```ts
import winston from "winston";
```

### Key Concepts

* **Transport** → where logs go
    
* **Level** → severity
    
* **Format** → structure
    

Your config:

```ts
new winston.transports.File({
  filename: "error.log",
  level: "error",
})
```

Means:

> Only errors go here nothing else

This is **production-grade logging**.

---

## 11\. Error Handling — Express Internals

```ts
app.use((err, req, res, next) => {
  res.status(err.statusCode || 500).json(...)
});
```

### Why 4 Parameters Matter

Express checks function length.  
4 params = error middleware  
3 params = normal middleware

This is **not optional**.

---

## 12\. Testing — Code Confidence

```ts
import request from "supertest";

const response = await request(app).get("/");
```

Supertest:

* Injects requests directly into Express
    
* No real server
    
* No ports
    
* Very fast
    

This is how **real backend tests are written**.
