Part One: Setting up Express, Typescript, MongoDB

Ermias Asmare
14 min readFeb 13, 2024

--

Welcome to the inaugural part of our series, “Mastering Express, Typescript, and MongoDB: A Comprehensive Guide to Building and Deploying Web Applications.” Today, we embark on an exciting journey where we’ll delve into setting up the foundational pillars of our project: Express, Typescript, and MongoDB. Additionally, we’ll delve into the critical realm of authentication, leveraging JWT and implementing email verification. Our ultimate goal? Crafting a dynamic portfolio website that showcases your skills and accomplishments. So, let’s dive in and lay the groundwork for our ambitious venture.

Prerequisite

  • Node js installed on your local machine(HERE)
  • Code editor such as Vs code (HERE)
  • MongoDB desktop app or create an account on MonogoDB Atlas

Okay now we can get started, the first thing we need to do is create a new repository for the project on GitHub and clone our project to our local machine. if you want to know more about GitHub you can watch this video by freeCodeCamp(HERE).

Okay now lets open a terminal on our local machine and clone the repo to our local machine.

cloning the repo

Once we clone our project, lets cd to the project folder and open it up using any code editor, am using vs code. then lets open the terminal on our vs code and start setting up a node or express project using the command.

npm init

okay now on our project folder you will see a file called package.json.

what is package.json: is the heart of Node.js system. It is the manifest file of any Node.js project and contains the metadata of the project.

{
"name": "express_tutorial",
"version": "1.0.0",
"description": "This is a comprenshive guide of express backend tutorial",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"engines": {
"node": ">=14.0.0",
"npm": ">=4.1.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jrermi12/EXPRESS_TUTORIAL.git"
},
"keywords": [
"Express"
],
"author": "Ermias Asmare",
"license": "ISC",
"bugs": {
"url": "https://github.com/jrermi12/EXPRESS_TUTORIAL/issues"
},
"homepage": "https://github.com/jrermi12/EXPRESS_TUTORIAL#readme"
}

Explanation:

  • name: The name of the application/project.
  • version: The version of application. The version should follow semantic versioning rules.
  • description: The description about the application, purpose of the application, technology used like React, MongoDB, etc.
  • main: This is the entry/starting point of the app. It specifies the main file of the application that triggers when the application starts. Application can be started using npm start.
  • scripts: The scripts which needs to be included in the application to run properly.
  • engines: The versions of the node and npm used. These versions are specified in case the application is deployed on cloud like heroku or google-cloud.
  • keywords: It specifies the array of strings that characterizes the application.
  • author: It consist of the information about the author like name, email and other author related information.
  • license: The license to which the application confirms are mentioned in this key-value pair.
  • dependencies: The third party package or modules installed using.
  • npm: are specified in this segment.
  • devDependencies: The dependencies that are used only in the development part of the application are specified in this segment. These dependencies do not get rolled out when the application is in production stage.
  • repository: It contain the information about the type and url of the repository where the code of the application lives is mentioned here in this segment.
  • bugs: The url and email where the bugs in the application should be reported are mentioned in this segment.

now that we have a better understanding of package.json lets get started by installing express.

npm install express
npm install -D @types/express @types/node typescript ts-node
  • typescript: The TypeScript compiler.
  • ts-node: Enables running TypeScript files directly with Node.js.
  • @types/express: Provides TypeScript definitions for Express.

Next let’s Configure the “tsconfig.json” file for Typescript settings. Create a file named “tsconfig.json” in the main directory of your project and add the following configuration:

tsconfig.json

{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"allowJs": true,

},
"lib": ["es2015"]
}

This configuration specifies the Typescript compiler options for your project:

  • "module": "commonjs": Generates CommonJS modules.
  • "esModuleInterop": true: Enables interoperability between CommonJS and ES6 modules.
  • "target": "es6": Specifies the ECMAScript target version to ES6.
  • "moduleResolution": "node": Resolves module imports using Node.js resolution algorithm.
  • "sourceMap": true: Generates source map files for debugging.
  • "outDir": "dist": Sets the output directory for compiled JavaScript files.
  • "allowJs": true: Allows TypeScript to compile JavaScript files along with TypeScript files.
  • "lib": ["es2015"]: Specifies the library files to include, in this case, ES2015 features.

This configuration ensures TypeScript compiles your code according to these settings, facilitating seamless integration with your project’s requirements.

Next lets Configure ESLint for TypeScript settings by creating a file named “.eslintrc.json” in the main directory of your project, and add the following configuration:

.eslintrc.json
{
"env": {
"browser": true,
"jest": true
},
"parser": "@typescript-eslint/parser",
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"plugins": ["@typescript-eslint"],
"rules": {}
}

This configuration specifies ESLint settings for TypeScript files:

  • "env": Specifies environments where your code will run. "browser": true indicates the code will run in a browser environment, and "jest": true indicates it will run in a Jest testing environment.
  • "parser": "@typescript-eslint/parser": Specifies the parser for TypeScript files.
  • "extends": Extends ESLint configurations, including "eslint:recommended" for general recommended rules and "plugin:@typescript-eslint/recommended" for TypeScript-specific recommended rules.
  • "plugins": Specifies ESLint plugins to use, including @typescript-eslint for TypeScript-specific linting rules.
  • "rules": Customizes ESLint rules. You can add specific rules here if needed.

This configuration ensures ESLint applies the appropriate linting rules for TypeScript files in your project, facilitating consistent code quality and style.

Okay we are done with setting up express with typescript now lets continue installing and setting up MongoDB and other necessary packages.

npm i dotenv mongoose
  • Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env
  • Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It manages relationships between data, provides schema validation, and is used to translate between objects in code and the representation of those objects in MongoDB.

if you have noticed your project folder a new folder is created by the name node_modules when you installed the packages mongoose and dotenv.

what is node_modules: node_modules is a folder which stores external dependencies of the project. Whenever we install some external package through “npm” or “yarn” in a project locally, it gets stored into the node_modules folder located at the root of the project directory by default

Before we start writing any code we will discuss about our project folder structure, a project folder structure is an essential part of any project. it has to be as clear and effective as possible. because you might be working on project with a team member or you might need to handover a project to another developer. the other developer should be able to understand how to project is setup and where each section of the code is placed through out the project. The image below shows the folder structure we are using for our app.

  • public/: This directory stores static files such as images, CSS, and JavaScript files.
  • src/: This directory contains all the source code for the application.
  • api/: Contains API endpoints or routes and their respective controllers.
  • config/: Houses configuration files such as Multer, MongoDB connection, cloudinary configuration, etc.
  • controllers/: Contains feature-specific controllers.
  • errors/: Contains error handling middleware.
  • interface/: Stores Typescript interfaces for MongoDB models
  • middleware/: Houses middleware functions such as JWT authentication.
  • models/: Contains MongoDB models.
  • Services/: This are functions that communicate to our database
  • utils/: Houses helper functions used throughout the application.

okay once we setup our project lets push the changes we made so far to our remote git repository, if you have noticed on the image below there is 929 changes on our project.

we all know that we did not make changes to 929 files in our project. this is happening due to the node_modules folder. as a rule node_modules are not supposed to be committed or pushed to our git repository. so there is a file called .gitignore where we can controller which files and folders are not to be added to git.

lets create the .gitignore file on the root directory of our project folder and add the snippet of code below

node_modules

now you can notice we have only 7 changes on our project, so lets commit them and push them to our remote repository. to commit and push our changes we can do it in so many different ways, we can do it from our terminal we can do it from vs code source control or install applications such as github desktop for windows and mac, gitkraken for linux. the choice is yours for this tutorial am gone use the vs code source control.

as shown on the image below am naming my first as commit as inital-commit

before you commit your changes make sure to get them to staged section, then click on commit.

then push your changes to the remote git repository by clicking on the sync Chnages button.

as you can see on the image above our changes are added to our remote git repository.

Branching Out for Better Development

While this tutorial won’t delve into the nitty-gritty of Git repositories, using multiple branches is a smart practice. Some developers even create a branch for each feature they work on.

The most common approach is to create a dedicated development branch, often called `dev`. This is your playground for coding, testing, and iterating on your project.

Here’s why branching is key:

  • Isolation: Development environments are separate from production environments. This means you can experiment without affecting the live version of your project.
  • Collaboration:Branches allow multiple developers to work on different parts of the codebase simultaneously, avoiding conflicts.
  • Deployment Flexibility: Branches provide a staging area for tested and approved changes before merging them into the production branch (often called `main`).

For simplicity, this blog will assume `main` is your production branch and `dev` is your development branch.

so lets create a branch called dev on our repo, once again there are alot of way to do this. but am gone use my terminal to perform this task.

i first checked the branch list we have by using the command “git branch”, then i used the command “git branch [NAME_OF_BRANCH]” to create a new branch.

git branch dev

okay now we have to check out from the main branch to dev branch. to do that use the command below

git checkout dev

Setting up our Express Server

lets install some necessary packages for running our server.

npm i morgan helmet cors
  • Morgan: Morgan is a middleware for logging HTTP requests in an Express.js server. It helps in logging details of incoming requests to your server, which is useful for debugging and monitoring
  • Helmet: Helmet is a middleware for Express.js that helps secure your applications by setting various HTTP headers. It provides protection against some common vulnerabilities by configuring headers appropriately..
  • Cors:(Cross-Origin Resource Sharing) is a security feature implemented in web browsers to prevent requests from one domain from accessing resources from another domain. However, sometimes you may want to allow cross-origin requests, for example, when building a web application that communicates with a separate API server.

In our “src” folder there is a file called “app.ts

import { config } from "dotenv";
import express from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";

config();

const app = express();

app.use(morgan("dev"));
app.use(helmet());
app.use(cors());
app.use(express.json());

export default app;

1 dotenv: This package is used to load environment variables from a `.env` file into `process.env`. This allows you to configure environment-specific variables outside your codebase.

2. express: A fast, unopinionated, minimalist web framework for Node.js that is used to build web applications and APIs.

3. morgan: HTTP request logger middleware for Node.js. It helps log requests to the console, which is useful for debugging and monitoring the traffic on your server.

4. helmet: Helmet helps secure Express apps by setting various HTTP headers to protect against well-known web vulnerabilities.

5. cors: This middleware provides Cross-Origin Resource Sharing (CORS) support, which is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the resource originated.

6. express.json(): This middleware is used to parse incoming requests with JSON payloads. It is a built-in middleware function in Express starting from version 4.16.0.

Finally, the configured Express application is exported:

Okay now lets work on our first api and middleware.

In our src folder there is a folder called middleware, lets creat a file called inde.middleware.ts add the code snippet below:

src/middleware/index.middleware.ts

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

export function notFound(req: Request, res: Response, next: NextFunction) {
res.status(404);
const error = new Error(`🔍 - Not Found - ${req.originalUrl}`);
next(error);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
const statusCode = res.statusCode !== 200 ? res.statusCode : 500;
res.status(statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack,
});
}

notFound Middleware: This middleware function handles requests to routes that do not exist. It sets the response status to 404 and creates an error with a message indicating that the requested URL was not found. This error is then passed to the next middleware in the stack.

errorHandler Middleware: This middleware function handles any errors that occur in the application. It checks the response status code, sets it to 500 if it hasn’t been set yet, and sends a JSON response containing the error message. In production, it hides the error stack trace.

okay now lets import our middleares to our app.ts files.

import { config } from "dotenv";
import express from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";

import { notFound, errorHandler } from "./middleware/index.middleware";

config();

const app = express();

app.use(morgan("dev"));
app.use(helmet());
app.use(cors());
app.use(express.json());

app.use(middlewares.notFound);
app.use(middlewares.errorHandler);

okay now, to try if our setup is working properly we have to configure an api-endpoint. lets create a file called “index.api.ts” inside the api folder.

src/api/index.api.ts


import express from "express";
const router = express.Router();

router.get("/", (req, res) => {
res.json({
message: "🦄🌈✨👋🌎🌍🌏✨🌈🦄",
});
});
export default router;

okay now lets import our api to the “app.ts”, and use it, this is the final code in the app.ts

// src/app.ts
import { config } from "dotenv";
import express from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";
import { notFound, errorHandler } from "./middleware/index.middleware";
import api from "./api/index.api";

config();


app.use(morgan("dev"));
app.use(helmet());
app.use(cors());
app.use(express.json());

app.use("/api/", api);

app.use(notFound);
app.use(errorHandler);

export default app;

okay for the last part of setting up our server lets go to our root directory of our project and on the file index.ts which is the entry point to our server. lets add the code snippet below:

import app from './src/app';

const port = process.env.PORT || 5000;
app.listen(port, () => {
/* eslint-disable no-console */
console.log(`Listening: http://localhost:${port}`);
/* eslint-enable no-console */
});

okay finally lets setup our scripts in package.json to start our express server. befor that we have to install once packge called “nodemon”

nodemon: is a tool that helps develop Node.js based applications by automatically restarting the node application when file changes in the directory are detected.

npm i nodemon 

package.json

"scripts": {
"dev": "nodemon index.ts",
"start": "tsc & node dist/index.js",
"build": "tsc",
"lint": "eslint . --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1"
},

Here’s a brief explanation of each script in the `scripts` section of your `package.json` file:

1. dev: Runs the application in development mode using `nodemon` to automatically restart the server when changes are detected. It watches `index.ts` for changes.

2. start: Builds the TypeScript code and then runs the compiled JavaScript file located in the `dist` directory.

3. build: Compiles the TypeScript code to JavaScript using the TypeScript compiler (`tsc`).
4. lint: Runs ESLint on all TypeScript files in the project to check for and enforce code quality and style rules.

5. test: Placeholder script for running tests, currently set to output an error message and exit with an error code.

These scripts help manage the development workflow, including running the server, building the project, linting the code, and running tests.

okay now lets run the server by using the command npm run dev on our terminal.

npm run dev

lets taste our setup lets open up Postman and test the api endpoint we created. The URL “http://localhost:5000/api/”.

okay our express server is up and running. now lets setup our express, typescript server to mongodb.

Setting up MongoDB

Okay let’s create a MongoDB database on MongoDB Atlas. i have explained it on my earlier blog, check it out Here.

then lets create .env file in the root directory of the project.

env file is used in projects to store configuration settings, environment variables, and sensitive information securely. It provides a convenient way to manage and organize various parameters that your project needs without hard-coding them directly into your code.

add the snippet of code below to the .env file

MONGO_DB_URI="YOUR_MONGODB_URL"
NODE_ENV=development
PORT=5000

Let’s setup our MongoDB connection configuration, by creating a file called “mongoose.ts” inside the “config” folder in the “src” ”directory of the app.

src/config/mongoose.ts

import { connect, set } from 'mongoose';

const MONGO_DB_URI = process.env.MONGO_DB_URI;

// connection to db
export const connectToDB = async () => {
try {
set('strictQuery', false);
const db = await connect(MONGO_DB_URI);
console.log('MongoDB connected to', db.connection.name);
// Emit an event when the connection is successful
} catch (error) {
console.error(error);
// Emit an event when there's an error
}
};

lets got to our “index.ts”, inside the main directory and update it with the snippet of code below.

import app from './src/app';
import { connectToDB } from './src/config/mongoose'; // Import the mongoose configuration file

connectToDB()
const port = process.env.PORT || 5000;
app.listen(port, () => {
/* eslint-disable no-console */
console.log(`Listening: http://localhost:${port}`);
/* eslint-enable no-console */
});

lets run our project once again and see if the mondob connection was successfull

Here it is our setup is working, in the next part of the series we will work on middlewares, error handlers, server optimization and more.

PART TWO : Enhancing our Express, Typescript and MongoDB project — Here

Thank you
Ermias Asmare

--

--