How to Setup an express Typescript backend server
Express.js is a lightweight, flexible Node.js framework that allows you to create robust API backend quick and easy. This article will help you setup a quick skeleton for the initial setup of your express server. You are free to change the any file or configuration according to your convenience.
Initial Setup
prerequisite- have node and npm or pnpm installed
Install the Initial dependencies and typescript dev dependencies that you need to build an express server.
npm init --y
/*or*/
pnpm init
npm install express
//or
pnpm install express cors dotenv
npm install -D typescript tsx ts-node @types/node @types/express @types/cors
//or
pnpm install -D typescript tsx ts-node @types/node @types/express @types/cors
Typescript Setup
Create tsconfig.json
npx tsc --init
// tsconfig.json
//uncomment these lines , these tell typescript where the source code writtern in typescript is and where to save the compiled code
"rootDir": "./src",
"outDir": "./dist",
//add this in your tscofig too , this tells typscript to ignore these folders when compiling so it will not compile unnecessary files, you can also use "include":["src"] if you want only want src to readable by the compiler
"exclude": ["node_modules", "dist"]
// package.json
// add type:"module" it allows nodejs to treat js files as ESM instead of commonJS -- means you can use the import {} syntax instead of old require syntax
"type": "module",
Creating the Server
Express App setup starts From the src folder which contains all your source code.
//create the src folder and the server entrypoint file
mkdir src && cd src
touch app.ts
touch server.ts
Before setting the server lets setup the config that will allow us to use our .env variables
This is the global config that will allow you to use your environment variables anywhere inside your application.
//inside src
mkdir config && touch config.ts
//config.ts
import dotenv from "dotenv";
dotenv.config();
interface Config {
port: number;
}
const config: Config = {
port: Number(process.env.PORT),
};
export default config;
//.env outside /src
PORT=8000
//app.ts
import express, { type Express } from "express";
const app: Express = express();
app.use(express.json());
export default app;
//server.ts
import app from "./app.js";
import config from "./config/config.js";
app.listen(config.port, () => {
console.log(`listenning on port ${config.port}`);
});
Creating A Route
//Global route
mkdir routes && touch routes/index.ts
//routes/index.ts - this is the global route the you can use for global routes like app.get(/helath) or module routes like app.use(/user,userRouter)
import { Router, type Request, type Response } from "express";
const router: Router = Router();
router.use("/", (req: Request, res: Response) => {
res.json("hello");
});
export default router;
// app.ts - add this line below app.use(express.json); to route all /api request to your global router
app.use("/api", router);
Testing the /api route
you can run the following commands from the root of your application or setup run scripts in your package.json
//package.json Go ahead and update your package.json to create run commands
//package.json
"scripts": {
"dev": "tsx watch src/server.ts", // command to start the dev server
"build": "tsc", // typscript compiles your code and places the js in the dist folder
"start": "node dist/server.js" // start the compiled js from the dist folder
},
// Now from your terminal run
npm or pnpm run dev
//visit http://localhost:8000/api - to get the hello from your server
Resource Setup
The Folder Structure for you to choose From
#1 Modern: Simplified Modules Folder structure for application with less modules.
src
├── modules
│ └── user
│ ├── user.controller.ts
│ ├── user.route.ts
│ ├── user.model.ts
│ └── user.service.ts
│
├── middlewares
├── services
├── routes (global)
├── types
├── app.ts
└── server.ts
#2 Classic: Classic Version allows grouping files by their types rather than by their parent module.
src
├── controllers
│ └── user.controller.ts
│
├── routes
│ └── user.routes.ts
│
├── models
│ └── user.model.ts
│
├── services
│ └── user.service.ts
│
├── middlewares
│
├── config
│
├── app.ts
└── server.ts
I simply prefer the modern modules structure on simpler projects as It is much more convenient and I don't have to jump through multiple Folders for updating a single module. But you can choose whichever structure you prefer .
Module Setup
Setting up the module.model.ts
//This is a model file that provides the necessary resources to the service layer .
//template user.model.ts
export const UserModel = {
findUserById: async (id: User["id"]) => {
//db call to find user by id
return user;
},
findUserByemail: async (email: string): Promise<User | null> => {
//db call to find the user and return
return user;
},
create: async (userData:User) => {
//create the user resource
return createdUser;
},
update: async (user: User) => {
//db call to update the user
return updatedUser;
},
};
Service layer - module.service.ts
//simplified service layer -> This layer is responsible for handling the checks module level validations , calls etc befor passing to the model layer
//temlate user.servce.ts
import { UserModel } from "./user.model.js";
export const register = async (
name: string,
email: string,
password: string,
) => {
return await UserModel.create(user);
};
export const login = async (email: string, password: string) => {
const existing = await UserModel.findUserByemail(email);
return existing;
};
export const updatePassword = async (
email: string,
oldPassword: string,
newPassword: string,
) => {
const existing = await UserModel.findUserByemail(email);
return updatedUser;
};
Controller entrypoint - module.controller.ts
// your Routers route to this controller and controller handles the request validation, json and status responses essesntially in a tryCatch block
// a template user.controller.ts
import type { Request, Response } from "express";
import { register, login, updatePassword } from "./user.service.js";
export const registerUser = async (req: Request, res: Response) => {
const { name, email, password } = req.body;
const newUser = await register(name, email, password);
return res.status(200).json(newUser);
};
export const loginUser = async (req: Request, res: Response) => {
const { email, password } = req.body;
const user = await login(email, password);
return res.status(200).json(user);
};
export const updateUserPassword = async (req: Request, res: Response) => {
const { email, oldPassword, newPassword } = req.body;
const updatedUser = await updatePassword(email, oldPassword, newPassword);
return res.status(200).json(updatedUser);
};
This was the modern Express Typescript backend setup, it did not covered the database setup which allows you to connect your headless application to a database. You can use modern ORM like prisma to connect your application to to your sql database or mongodb for nosql database.



