Skip to content

Setup an Express App with Typescript

Posted on:September 15, 2023 at 10:00 AM

This post will explain how to setup up a basic hello world express app with typescript, we will be using pnpm as the package manager.

Table of contents

Open Table of contents

Basic Project Setup

Initialize the Project

Initialize the folder as a pnpm project

pnpm init

Installing Packages

Install typescript and type declarations for node as dev dependencies.

pnpm i -D typescript @types/node
pnpm i -D tsx

Install TSX as the typescript runner.

pnpm i -D tsx

Install Express with it’s typings.

pnpm i express
pnpm i -D @types/express

Configuring Typescript

We will be using ESM (ECMA Script Module) syntax. So, we need to add an extra property called type with value as module to package.json

package.json
{
"name": "express-app",
"version": "1.0.0",
"type": "module"
...
}

Then add a tsconfig.json file to the root of project

tsconfig.json
{
"compilerOptions": {
"strict": true,
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "Bundler",
"moduleDetection": "force",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src", "scripts"]
}

You can use your own tsconfig.json file or checkout a cool repo called @tsconfig/bases.

Loading Env Variables

We will be using dotenv for loading variables from .env file, and zod for parsing and validating the env variables.

pnpm i dotenv zod

Creating a file to load ENV variables.

src/env.ts
import "dotenv/config";
import { z } from "zod";
export const envSchema = z.object({
PORT: z.coerce.number().default(8080),
});
export default envSchema.parse(process.env);

This will load env variable a .env file which looks something like this,

.env
PORT = 3000

Writing the Server

This is a basic hello world server. We are importing PORT from this env.ts file we just made.

src/index.ts
import express from "express";
import env from "./env";
const app = express();
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(env.PORT, () => {
console.log(`Listening on http://localhost:${env.PORT}`);
});

Now, we can use tsx to run typescript file directly without manual transpilation.

pnpm tsx src/index.ts

Building the Project (OPTIONAL)

We can use tools like esbuild to bundle the entire code to a single js file, which we can run directly with Node.

pnpm i esbuild

Then setting up a custom build script to bundle .ts files with ESM syntax to .mjs file with ESM Syntax. We also need toi include a custom header for now because of an issue with esbuild.

scripts/build.ts
import { build, BuildOptions } from "esbuild";
const config = {
entryPoints: ["src/index.ts"],
outdir: "./dist",
format: "esm",
outExtension: {
".js": ".mjs",
},
platform: "node",
bundle: true,
minify: true,
sourcemap: false,
banner: {
js: `
const require = (await import("node:module")).createRequire(import.meta.url);
const __filename = (await import("node:url")).fileURLToPath(import.meta.url);
const __dirname = (await import("node:path")).dirname(__filename);
`,
},
} satisfies BuildOptions;
try {
await build(config);
console.log("⚡️ Build success");
process.exit(0);
} catch (e) {
console.log("☠️ Build failed");
process.exit(1);
}

For compiling the source code use the command below. This will create a dist folder with index.mjs file in it.

pnpm tsx scripts/build.ts

After compiling everything to a single file we can run it like this,

node dist/index.mjs

It doesn’t even need any of the external dependencies to be installed, which is super cool 😎