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 initInstalling Packages
Install typescript and type declarations for node as dev dependencies.
pnpm i -D typescript @types/nodepnpm i -D tsxInstall TSX as the typescript runner.
pnpm i -D tsxInstall Express with it’s typings.
pnpm i expresspnpm i -D @types/expressConfiguring 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
{ "name": "express-app", "version": "1.0.0", "type": "module" ...}Then add a tsconfig.json file to the root of project
{ "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 zodCreating a file to load ENV variables.
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,
PORT = 3000Writing the Server
This is a basic hello world server. We are importing PORT from this env.ts file we just made.
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.tsBuilding 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 esbuildThen 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.
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.tsAfter compiling everything to a single file we can run it like this,
node dist/index.mjsIt doesn’t even need any of the external dependencies to be installed, which is super cool 😎