avatarNikit Rauniyar

Summary

This context provides a comprehensive guide on setting up a Node.js project with TypeScript, including creating a clean folder structure, configuring ESLint and Prettier, connecting to a MongoDB database, and building a basic API server with CRUD operations.

Abstract

The provided content serves as a step-by-step tutorial for developers to initialize a Node.js project using TypeScript, which is a strongly typed superset of JavaScript. It outlines the process of setting up a Node API server, beginning with initializing a project with npm, setting up the TypeScript compiler, and installing necessary dependencies such as Express and Mongoose. The guide also emphasizes the importance of code quality tools like ESLint and Prettier, and demonstrates how to organize code into a clean folder structure with separate directories for configurations, controllers, middlewares, models, routers, and utilities. Additionally, it covers the practical aspects of connecting to a MongoDB database using MongoDB Atlas and creating a simple user API with endpoints for user creation and retrieval. The tutorial concludes by acknowledging the need for further enhancements such as global error handling, data validation, and security measures like password hashing.

Opinions

  • The author suggests that JavaScript's weak typing can lead to difficulties in maintaining a large codebase, and thus recommends TypeScript for its strong typing features.
  • It is implied that using npx is a convenient way to execute npm packages without installing them explicitly.
  • The author expresses the benefits of using .env files to manage environment variables securely.
  • The use of ts-node-dev for development is recommended for its ability to automatically restart the server upon code changes.
  • Code formatting and consistency are highlighted as important, with the recommendation to use Prettier.
  • The author advocates for the separation of concerns in a Node.js project by organizing code into distinct folders for different aspects of the application.
  • MongoDB Atlas is presented as a preferred choice for database connection, indicating its ease of use and integration with Node.js applications.
  • The tutorial acknowledges the importance of additional security practices, such as password hashing, which are essential for production-ready applications but are beyond the scope of the current guide.

How to set up a Node.js project with a clean folder structure (TypeScript)

A step-by-step guide to creating a Node API server

(image from datasoft)

In this step-by-step tutorial, we will learn how to initialize a node project. Node is not a programming language but a run-time environment that enables JavaScript to run on server-side. However, JavaScript is a weakly typed language, and often in large code-base it is difficult to read, understand codes/logics and find bugs.

TypeScript is a super-set of JavaScript which is a strongly typed language. A TypeScript project is written and later transpiles into JavaScript which then can be run by node run-time environment.

Prerequisites

  • A basic knowledge of JavaScript/TypeScript
  • Postman or similar tool to test API
  • A basic understanding of MongoDB or any other database
  • VS Code or any code editor of your choice
  • Node.js installed on your operating system (Install Node.js)

Step 1: Create a folder and initialize with npm

Open up your terminal, create a folder and move into that:

mkdir node-tutorial
cd node-tutorial

After moving into that directory, initialize a project with npm:

npm init -y

You can include or exclude the -y flag which means “yes” to all the defaults. Open the folder in VS Code, where you will see a package.json file.

Create an index.ts file on that directory, which will serve as an entry point for your API server. After this, your folder structure will look like this:

Step 2: Install dependencies and create a Typescript-Express server

Setup TypeScript compiler:

npm install --save-dev typescript ts-node ts-node-dev

Here, the — save-dev flag is used because these packages are only required at the time of development mode. In actual deployment, typescript transpiles into JavaScript, and packages installed with the — save-dev can be omitted for performance.

Initialize tsconfig.json file using npx:

npx tsc --init

npx : An npm package runner — helps to execute packages without installing explicitly.

Now, install express library to create an API server and dotenv to use environment variables:

npm install express dotenv

Install types for express as a dev dependency:

npm install --save-dev @types/express

Sometimes with many npm packages @types/<package-name> should be installed for type support.

Write your server code in index.ts and expose it in port 8000:

import express, { Application } from 'express';
import 'dotenv/config';

const app: Application = express();

const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
 console.log(`Server is up and running on port ${PORT}`);
});

Add a script with dev in your package.json to run your server using ts-node-dev:

{
  "name": "tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "ts-node-dev --respawn index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/express": "^4.17.16",
    "ts-node": "^10.9.1",
    "ts-node-dev": "^2.0.0",
    "typescript": "^4.9.4"
  },
  "dependencies": {
    "dotenv": "^16.0.3",
    "express": "^4.18.2"
  }
}

Start your server:

npm run dev

Step 3: Configure ESlint and prettier

Eslint is a plugin which will help us to identify and report bugs along with making code more consistent. Prettier is another tool that makes code formatting easier across all files.

npm install --save-dev eslint prettier

Initialize ESlint:

npm init @eslint/config

Create .prettierrc.js file on your project.

module.exports = {
    singleQuote: true,
    semi: true,
    trailingComma: 'none',
    printWidth: 120,
    tabWidth: 4
};

Create .eslintignore and .prettierignore files and put below codes respectively:

node_modules
coverage
cdk.out
.eslintrc.js
dist
package.json
CHANGELOG.md
*.yml
.versionrc

Step 4: Create directories

Create a folder src where we will keep all the source codes including moving index.ts to src with different subfolders:

mkdir src
cd src
mkdir config controllers middlewares models routers utils

We will keep files in these sub-folders:

  • config: Any configuration files like database connection, external API integration.
  • controllers: End functions which deal with database requests and response.
  • middlewares: Any middleware functions between router and controllers.
  • models: All the schema for database.
  • routers: Express router to route based on different end-points.
  • utils: Any utility function used in the project.

Remember to change dev script in package.json from index.ts to src/index.ts

After all the changes, your project structure should look like this:

Step 5: Configure connection with a database

Now we will connect to a database. In this case, I will use MongoDB Atlas. You can also use MongoDB Atlas. To setup MongoDB Atlas, you can follow this tutorial (https://hevodata.com/learn/mongodb-atlas-nodejs/).

Install mongoose and create mongodbConnect.ts inside config:

npm install mongoose
touch src/config/mongodbConnect.ts

Paste this code in you mongodbConnect.ts:

import mongoose from 'mongoose';
import 'dotenv/config';

mongoose.set('strictQuery', false);

const mongodbConnect = async () => {
    try {
        await mongoose.connect(process.env.MONGODB_URI as string);
        console.log('MongoDB Connected');
    } catch (error: any) {
        console.log(error.message);
    }
};

export default mongodbConnect;

Your MONGODB_URI is the connection string that you can get from you MongoDB Atlas account in the web. Put the value in your .env file.

MONGODB_URI=mongodb+srv://<username>:<password>@rbac.m03getg.mongodb.net/?retryWrites=true&w=majority

After database connection, you can now create models in MongoDB and use them in your APIs. You should restart your server manually after these with ctrl+c and npm run dev in your terminal window. You should see information like this in your terminal.

Step 6: Create and expose your first API

Create files in respective directories src/routers/userRouter.ts, src/models/userSchema.ts and src/controllers/userController.ts

touch src/models/userSchema.ts src/routers/userRouter.ts src/controllers/userController.ts

In userSchema.ts:

import { Schema, model } from 'mongoose';

export interface IUser {
    firstName: string;
    lastName: string;
    email: string;
    password: string;
}

export const userSchema: Schema<IUser> = new Schema({
    firstName: String,
    lastName: String,
    email: String,
    password: String
});

const User = model<IUser>('User', userSchema);

export default User;

In userController.ts:

import { Request, Response } from 'express';
import User from '../models/userSchema';

const createUser = async (req: Request, res: Response) => {
    try {
        const { firstName, lastName, email, password } = req.body;

        const newUser = new User({
            firstName,
            lastName,
            email,
            password
        });

        const savedUser = await newUser.save();

        res.status(201).json(savedUser);
    } catch (error: any) {
        console.log(error.message);
        res.status(500).json({ error: error.message });
    }
};

const getUser = async (req: Request, res: Response) => {
    try {
        const { userId } = req.params;
        const user = await User.findOne({ _id: userId });

        if (!user) {
            res.status(404).json({ error: 'User not found' });
            return;
        }

        res.status(200).json(user);
    } catch (error: any) {
        console.log(error.message);
        res.status(500).json({ error: error.message });
    }
};

export default { createUser, getUser };

In userRouter.ts:

import express, { Router } from 'express';
import userController from '../controllers/userController';

const userRouter: Router = express.Router();

// API route: /users/
userRouter.post('/', userController.createUser);

// API route: /users/:userId
userRouter.get('/:userId', userController.getUser);

export default userRouter;

Modify your index.ts:

import express, { Application } from 'express';
import 'dotenv/config';
import mongodbConnect from './config/mongodbConnect';
import userRouter from './routers/userRouter';

const app: Application = express();

// Database Connection
mongodbConnect();

// Middleware Functions
app.use(express.json());

// Routers
app.use('/users', userRouter);

const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
    console.log(`Server is up and running on port ${PORT}`);
});

Test Your APIs using Postman:

API: Create a User
API: Get a User

Conclusion

You have successfully built an API server with Node-TypeScript. Obviously, there are a lot of things to do such as, global error handling, data validation middleware, storing passwords in hash, authentication & authorization and a lot more. However, this tutorial was mainly focused on setting up minimal Node-TypeScript API server with database connection, clean folder structure, eslint, prettier, and separating controllers, middlewares, and routers.

Nodejs
Expressjs
Typescript
Mongodb
API
Recommended from ReadMedium