TypeScript Tutorial: Building a Simple Web-Based URL Shortener

In today’s fast-paced digital world, sharing long, unwieldy URLs can be a real pain. They’re difficult to remember, share, and sometimes even break in emails or social media posts. That’s where URL shorteners come in. They take a long URL and transform it into a much shorter, more manageable one, making it easier to share and track. This tutorial will guide you through building a basic URL shortener using TypeScript, providing you with a practical project to hone your TypeScript skills and understand the fundamentals of web development.

Why Build a URL Shortener?

Building a URL shortener isn’t just a fun coding exercise; it’s a great way to learn and apply various programming concepts. You’ll get hands-on experience with:

  • TypeScript Fundamentals: Types, interfaces, classes, and more.
  • Backend Development: Handling HTTP requests and responses.
  • Database Interaction: Storing and retrieving data. (We’ll use a simple in-memory database for this tutorial).
  • API Design: Creating a RESTful API.
  • Error Handling: Properly handling and reporting errors.

By the end of this tutorial, you’ll have a functional URL shortener and a solid understanding of how these different components work together. You’ll also be equipped to build more complex web applications.

Prerequisites

Before we dive in, make sure you have the following:

  • Node.js and npm (or yarn): You’ll need Node.js installed on your machine to run the server and manage dependencies.
  • TypeScript: Globally install the TypeScript compiler: npm install -g typescript
  • A Code Editor: Visual Studio Code (VS Code) is highly recommended, but you can use any editor you prefer.
  • Basic JavaScript Knowledge: Familiarity with JavaScript concepts will be helpful.

Project Setup

Let’s get started by setting up our project. Open your terminal and follow these steps:

  1. Create a Project Directory: Create a new directory for your project and navigate into it:
mkdir url-shortener
cd url-shortener
  1. Initialize npm: Initialize a new npm project:
npm init -y
  1. Install Dependencies: Install the necessary dependencies. We’ll use Express.js for our web server and a few TypeScript-related packages:
npm install express typescript @types/express ts-node --save-dev
  • express: A fast, unopinionated, minimalist web framework for Node.js.
  • typescript: The TypeScript compiler.
  • @types/express: TypeScript definitions for Express.js.
  • ts-node: A TypeScript execution environment for Node.js, which allows us to run TypeScript files directly without compiling them first.
  1. Create TypeScript Configuration: Create a tsconfig.json file in your project root. This file configures the TypeScript compiler. You can generate a basic one using the TypeScript compiler:
npx tsc --init --rootDir src --outDir dist --esModuleInterop --resolveJsonModule --lib es6 --module commonjs

This command creates a tsconfig.json file with some default settings. You’ll likely want to customize this file as your project grows. Here’s a basic example:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"]
}
  1. Create Project Structure: Create the following directory structure:
url-shortener/
├── src/
│   ├── index.ts
│   └── ...
├── dist/
├── tsconfig.json
├── package.json
└── ...

Building the Backend (Server-Side)

Now, let’s build the backend of our URL shortener. This will handle the logic of shortening URLs and redirecting users to the original URLs.

1. Create the Entry Point (index.ts)

Inside the src directory, create a file named index.ts. This will be the main entry point of our application. Add the following code:

import express, { Request, Response } from 'express';

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

// Middleware to parse JSON request bodies
app.use(express.json());

app.get('/', (req: Request, res: Response) => {
  res.send('URL Shortener API');
});

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

Let’s break down this code:

  • We import the express module and define the types for Request and Response.
  • We create an Express application instance: const app = express();
  • We define the port the server will listen on. We check for an environment variable PORT, and if it’s not set, we default to 3000.
  • app.use(express.json());: This middleware is crucial. It parses incoming requests with JSON payloads (like the ones we’ll send to shorten URLs) and makes the parsed data available in the req.body property.
  • We create a basic route for the root path (/) to check if the server is running.
  • Finally, we start the server using app.listen().

2. Implement the Shorten URL Endpoint

Next, we’ll create the API endpoint that will handle shortening URLs. Add the following code inside index.ts, before the app.listen() call:

import { randomBytes } from 'crypto';

interface URLMapping {
  shortUrl: string;
  originalUrl: string;
}

const urlMap: URLMapping[] = []; // In-memory database (for simplicity)

app.post('/shorten', async (req: Request, res: Response) => {
  const { originalUrl } = req.body;

  if (!originalUrl) {
    return res.status(400).json({ error: 'Original URL is required' });
  }

  try {
    // Generate a short URL (a simple example)
    const shortUrl = generateShortUrl();
    urlMap.push({ shortUrl, originalUrl });
    res.status(201).json({ shortUrl: `/${shortUrl}` }); // Return short URL
  } catch (error: any) {
    console.error(error);
    res.status(500).json({ error: 'Failed to shorten URL' });
  }
});

function generateShortUrl(): string {
  // Generate a random string (e.g., 6 characters)
  return randomBytes(3).toString('hex'); // 3 bytes * 2 hex chars/byte = 6 chars
}

Let’s break down this code:

  • Import `randomBytes` from ‘crypto’: This is used to generate random strings for our short URLs.
  • URLMapping Interface: Defines the structure of our URL mappings (short URL and original URL).
  • urlMap (In-memory Database): This is a simple array to store our URL mappings. In a real-world application, you’d use a database like MongoDB, PostgreSQL, or MySQL.
  • POST /shorten Endpoint: This endpoint handles the shortening of URLs:
    • It expects a JSON payload in the request body with a `originalUrl` property.
    • It checks if the `originalUrl` is provided; if not, it returns a 400 Bad Request error.
    • It calls the generateShortUrl() function to generate a short URL.
    • It adds the original URL and the generated short URL to the urlMap.
    • It returns a 201 Created status code along with the shortened URL.
    • It includes error handling using a try...catch block.
  • generateShortUrl() function: This function generates a short URL. We use randomBytes() to generate random bytes and then convert them to a hexadecimal string. This is a simplified approach; in a real application, you might use a more robust short URL generation algorithm, such as base62 encoding.

3. Implement the Redirect Endpoint

Now, let’s create the endpoint that redirects users to the original URL when they visit the short URL. Add the following code inside index.ts, before the app.listen() call:

app.get('/:shortUrl', (req: Request, res: Response) => {
  const { shortUrl } = req.params;
  const mapping = urlMap.find((item) => item.shortUrl === shortUrl);

  if (!mapping) {
    return res.status(404).json({ error: 'Short URL not found' });
  }

  res.redirect(mapping.originalUrl); // Redirect to the original URL
});

Let’s break down this code:

  • GET /:shortUrl Endpoint: This endpoint handles the redirection to the original URL. The :shortUrl part is a route parameter.
  • It retrieves the shortUrl from the request parameters (req.params).
  • It searches the urlMap for a matching short URL.
  • If a match is not found, it returns a 404 Not Found error.
  • If a match is found, it redirects the user to the original URL using res.redirect().

Testing the Backend

Before moving on to the frontend, let’s test our backend using curl or a tool like Postman.

1. Start the Server

Open your terminal and run the following command to start the server:

npx ts-node src/index.ts

This will compile and run your TypeScript code. You should see a message in the console: Server is running on port 3000 (or the port you configured).

2. Test the /shorten Endpoint

Open a new terminal window or tab and use curl to send a POST request to the /shorten endpoint. Replace <your_url_here> with the URL you want to shorten:

curl -X POST -H "Content-Type: application/json" -d '{"originalUrl":"<your_url_here>