URL Shortener Tutorial · Module 01 of 11

Foundations & Project Setup

By the end of this tutorial you'll have a Git repo, a TypeScript HTTP server that says "hello", a working linter, a one-page design doc, and the habits that make every later module easier.

~2–3 hrsBeginnerNo prior code needed
← Back to Module 01 overview
Before You Start

Prerequisites

  • Node.js 20+ — install from nodejs.org or via nvm / fnm / volta.
  • Gitgit --version should print something.
  • A GitHub account (free) and the gh CLI installed and authenticated, or just be ready to create a repo via the web UI.
  • An editor — VS Code is the path of least resistance.
What You'll Have at the End

Definition of Done

  • A GitHub repo named url-shortener with main as the default branch.
  • A TypeScript Express server running locally on http://localhost:3000.
  • npm run lint and npm run dev both work.
  • A one-page docs/design.md committed to the repo.
  • Branch protection on main requiring PRs.
The Steps

Build It

STEP 1

Create the repo

From an empty parent folder:

mkdir url-shortener && cd url-shortener
git init -b main
gh repo create url-shortener --public --source=. --remote=origin
# Or skip the gh command and create the repo via github.com later.
✓ Verify: git status shows "On branch main".
STEP 2

Initialize the Node project

npm init -y
npm pkg set type="module"
npm pkg set engines.node=">=20"
✓ Verify: package.json exists and contains "type": "module".
STEP 3

Install TypeScript and runtime dependencies

npm install express
npm install -D typescript tsx @types/node @types/express \
              eslint @eslint/js typescript-eslint prettier
Gotcha: If npm install hangs on a corporate network, set npm config set registry https://registry.npmjs.org/ and try again.
STEP 4

Configure TypeScript

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}
STEP 5

Write the hello-world server

Create src/server.ts:

import express from 'express';

const app  = express();
const port = Number(process.env.PORT ?? 3000);

app.get('/health', (_req, res) => res.json({ ok: true }));
app.get('/',       (_req, res) => res.send('URL Shortener — hello!'));

app.listen(port, () => console.log(`listening on :${port}`));
STEP 6

Add npm scripts

npm pkg set scripts.dev="tsx watch src/server.ts"
npm pkg set scripts.build="tsc"
npm pkg set scripts.start="node dist/server.js"
npm pkg set scripts.lint="eslint src"
npm pkg set scripts.format="prettier --write ."

Run it:

npm run dev
✓ Verify: open http://localhost:3000/health in a browser. You should see {"ok":true}.
STEP 7

Set up linting and formatting

Create eslint.config.js:

import js from '@eslint/js';
import tseslint from 'typescript-eslint';

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  { ignores: ['dist/**', 'node_modules/**'] }
];

Create .prettierrc.json:

{ "singleQuote": true, "semi": true, "printWidth": 100 }
✓ Verify: npm run lint exits with code 0.
STEP 8

Add an .editorconfig and .gitignore

.editorconfig:

root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

.gitignore:

node_modules
dist
.env
.env.*
!.env.example
*.log
.DS_Store
STEP 9

Write the design doc

Create docs/design.md with this skeleton — keep it to one page.

# URL Shortener — Design

## Problem
Users want short, shareable URLs that redirect to long ones.

## Goals
- POST /shorten → returns a short code
- GET /:code → 302 redirects to the original URL
- Per-user ownership of links
- 99.95% redirect availability over 30 days

## Non-goals (v1)
- Custom domains
- Click analytics dashboard (will arrive in module 6)
- Mobile app

## Sketch
Client → Express → Redis (cache) → Postgres
                ↘ Queue → Worker → clicks table

## Open questions
- ID scheme: random base62 vs hash? (decide in module 2)
- Anonymous quota? (decide in module 5)
STEP 10

Commit, push, protect

git add .
git commit -m "module 01: scaffold typescript express server"
git push -u origin main

On GitHub: Settings → Branches → Add rule for main:

  • Require a pull request before merging.
  • Require status checks to pass (we'll add the actual checks in Module 08).
  • Disallow direct pushes to main.
✓ Verify: attempt git push origin main from a new commit on main directly — it should be rejected.
Common Gotchas

If Something Goes Wrong

  • "Cannot find module 'express'" — make sure you ran npm install after creating package.json.
  • tsx not found — verify npx tsx --version works; if not, reinstall: npm i -D tsx.
  • Port 3000 in use — set PORT=3001 npm run dev (or kill the other process).
  • ESLint complains about process — make sure @types/node is installed.
What's Next

Move On

You've got a clean slate. Next up: actually doing something useful with it — designing the API and shaping the database.