How To Build A Twitter Clone With NestJS, Prisma And React ( Part 1 )
Tags: node, nestjs, prisma, react
Published: 2020-09-12 19:22:26

---
title: How To Build A Twitter Clone With NestJS, Prisma And React ( Part 1 )
published: true
tags: nodejs,nestjs,prisma,react
canonical_url: https://darioielardi.hashnode.dev/how-to-build-a-twitter-clone-with-nestjs-prisma-and-react-part-1
---

> This tutorial has been originally published on my [Hashnode blog](https://darioielardi.hashnode.dev/how-to-build-a-twitter-clone-with-nestjs-prisma-and-react-part-1).

- Part 1: Setup & first steps ( this article )
- [Part 2: Authentication](https://dev.to/darioielardi/how-to-build-a-twitter-clone-with-nestjs-prisma-and-react-part-2-2ah8)
- Part 3 Coming Soon

---

## Overview

In this tutorial we are going to explore in details the process of building a Twitter clone as a complete web application, which will consist of a [React](https://reactjs.org) [single page application](https://en.wikipedia.org/wiki/Single-page_application), backed by an API server built with [NestJS](https://nestjs.com) and [Prisma](https://prisma.io).

The features we are going to implement are:

- Read tweets feed
- Post a tweet
- Visit users' profile
- Follow other users
- Likes & replies

### Requirements

- Basic web APIs & HTTP knowledge
- NodeJS & npm
- Typescript ( and Javascript )
- PostgreSQL basic knowledge
- React basics ( with hooks )

## Setup

We need a Postgres instance with a brand new database to store our application data. Once you installed Postgres ( you can use [Postgres App](https://postgresapp.com/), [Docker](https://docker.com) or the [official installer](https://www.postgresql.org/download/) ) you have to create a new database. Just open up your favorite terminal client and run `psql` to start a Postgres shell session. You can now create the new database simply running the corresponding SQL command: `CREATE DATABASE "twitter";`.

Next we need to install the NestJS CLI:

```bash
npm i -g @nestjs/cli
```

At the time of writing, the last Nest CLI version is `7.5.1`.

Now we can use it to scaffold our project inside a `twitter-clone` folder. Feel free to choose your favorite package manager when prompted, I'm going to use `npm`.

```bash
mkdir twitter-clone && cd twitter-clone
nest new twitter-api
```

Let's open up your favorite editor and look at the project structure.

We can see a bunch of configuration files, a `test` folder, and finally, an `src` folder where all the code we'll write will live.

Let's open up the `main.ts` file, which is the entry point of our application.

Here we can immediately notice the only declared function, the `bootstrap` function, which instantiates our Nest application and makes it listen for requests on port `3000`.

To test this out let's start our server:

```bash
npm run start:dev
```

Every time a file changes in our project directory, the Nest CLI will take care of restarting the server.

Open up your favorite HTTP client ( I'm going to use [HTTPie](https://httpie.org/), which is a nice `curl` alternative, but you can also use a GUI based one such as [Postman](https://www.postman.com/) ) and try to send a request to our server.

```bash
http localhost:3000
```

We should see `Hello World!` as the response. Our server is working!

Let's now take a look behind the scenes.

## NestJS Fundamentals

In the `bootstrap` function we can see how our Nest application is instantiated from the `AppModule` class by the `create` factory function. NestJS promotes a modular application structure, which means that we are supposed to organize every "feature", with its own set of capabilities, within its own module.

The root module of our application is the `AppModule`. Let's open up the `app.module.ts` file.

```typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

As you can see a module is just a class with a `@Module` decorator ( if you're not familiar with the concept of decorators I strongly recommend reading the dedicated [page](https://www.typescriptlang.org/docs/handbook/decorators.html) in the Typescript handbook since we will frequently use them throughout this tutorial ).
The `@Module` decorator takes a single object whose properties are:

- `controllers`: a list of classes in charge of handling http requests.
- `providers`: a list of classes ( or services ) which encapsulate business logic. It could consist of module-specific features or global utilities, or even external classes exported by third-party packages.
- `imports`: a list of modules imported by this module. This allows the module to take advantage of other modules' functionalities. We'll see and discuss this feature later on.

Let's now take a look at the `AppController` class.

```typescript
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
```

The first thing we can see is the `Controller` decorator on top of the class declaration, which tells Nest that we want to use this class to handle http requests.
The second thing is the presence of a parameter in the class `constructor`, whose type is at the moment the only provider in this module, the `AppService` class.
NestJS will take care of injecting an instance of this class every time the controller will need it ( more on this later ), thanks to its powerful [dependency injection](https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/) system.

Let's now focus on the `getHello` method. The `Get` decorator is a way to map this method to an endpoint and an HTTP verb. Sending a GET request to `localhost:3000/` it will be handled by this method. To specify a different path we can add a `string` parameter like this:

```typescript
@Get('hello')
```

This way the mapped endpoint will now be `localhost:3000/hello`, while a request to the base path `/` would trigger a 404 HTTP error because there is no method to handle it.

We can also add a `string` parameter to the `Controller` decorator to add a path prefix to all methods.
More on controllers and endpoints mapping in the dedicated [page](https://docs.nestjs.com/controllers) in the official NestJS documentation.

As we can see the only thing this method is doing is calling the `getHello` method of the `AppService` class. This is because controllers are not supposed to hold business logic, the same way services are not supposed to handle endpoints mapping, following the [single-responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle).

Let's now take a look at the last piece of the puzzle, the `AppService` class.

```typescript
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
```

The most important thing here is the `Injectable` decorator. This decorator tells NestJS that this service is going to be used as a provider ( for example by the `AppController` ), thus we need it to be handled by the dependency injection system.

The `getHello` method is just returning the `Hello World!` string, which now we know where it was coming from.

Let's now begin with our features implementation.

## The users module

The first thing we are going to implement in our application is user management.

Let's generate the users module with the Nest CLI:

```bash
nest generate module users
```

This will generate a new `users` folder in the `src` directory, which will contain a `users.module.ts` file with an empty module declaration.

Let's add a controller:

```bash
nest generate controller users
```

The Nest CLI will not only generate the controller file and class, but it will also add the new controller to the `controllers` list of the module in the file with the same path and prefix ( `users/users.module.ts` ).

The new controller will also have the `users` string as a path parameter in the `Controller` decorator because Nest assumes every endpoint mapped by this class will begin with this prefix.

Along with this file Nest will generate the `users.controller.spec.ts` file. A file like this will be generated for almost every generated file, and this is where we are supposed to write our tests. Let's leave it aside for now.

Let's now generate the users service:

```bash
nest generate service users
```

This time Nest will generate a `UsersService` class within the users module with the `Injectable` decorator on top and will also add it to the `providers` parameter of the users module.

To implement our business logic we now need to setup Prisma.

### Prisma setup

[Prisma](https://prisma.io) is a relatively new data access framework for NodeJS written in Typescript, which makes it a particular fit for our project. It takes care of migrations ( this is an experimental feature at the time of this tutorial ) and it generates a complete, type-safe Typescript client to access and manage our data.

Let's install the Prisma CLI and run the init command.

```bash
npm install @prisma/cli --save-dev
npx prisma init
```

At the time of this tutorial, the last Prisma version is `2.6.2`.

Prisma will use the `DATABASE_URL` environment variable declared in the generated `prisma/.env` file, so let's adapt it to match our database connection string. In my case, it looks like this ( those are the default parameters if you installed Postgres through the [Postgres App](https://postgresapp.com/) ):

```bash
DATABASE_URL="postgresql://postgres:secret@localhost:5432/twitter?schema=public"
```

Let's now add a new model to the Prisma data model in the `prisma/schema.prisma` file.

Our user table will have a `username` column as the primary key since it will be unique for every user, and also a password and a display name.

```prisma
model User {
  username    String @id
  password    String
  displayName String
}
```

To generate and apply the migration run the following commands:

```bash
npx prisma migrate save --name users --experimental
npx prisma migrate up --experimental
```

If everything goes well a new `User` table will be created in your database.

We can now generate the Prisma client with the following command:

```bash
npm install @prisma/client
```

This will automatically tell Prisma to generate the client in the `node_modules/.prisma/client` directory, and it will be referenced and exported by the `@prisma/client` package to be imported by us in our project. Specifically, it generates a `PrismaClient` class, which we'll be using every time we need to access our database.

To use Prisma in our application we might think to import the client directly in our services, but that would be the wrong way to go. We definitely want to take advantage of the Nest dependency injection system, to let the framework handle instantiation and injection when it needs to, keeping our application fast and our project structure clean and well organized.

This is another perfect use case for `providers`. All we have to do is to write a class that will extend the generated `PrismaClient` class and makes it `Injectable`.

```typescript
// src/prisma.service.ts

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}
```

Our `PrismaService` also need to call the `$connect` method when the service is instantiated by the framework to connect to the database and the `$disconnect` method on application shutdown. To do that our class needs to implement the `onModuleInit` and `onModuleDestroy` methods declared in the interfaces with the same name, which will be called by the framework at the right moment.

Now that we have our prisma service we can import it in our users module to be used in the users service.

```typescript
// users.module.ts

// ..
import { PrismaService } from '../prisma.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService, PrismaService],
})
// ...
```

### Our first endpoints

Let's now implement the following endpoints:

- `GET /users/:username`: get a user by his username
- `POST /users`: create a user

We can easily write the logic for the first one in our `UsersService`:

```typescript
// users.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { User } from '@prisma/client';
import { PrismaService } from '../prisma.service';

@Injectable()
export class UsersService {
  constructor(private db: PrismaService) {}

  async findOne(username: string): Promise<User> {
    const user = await this.db.user.findOne({
      where: { username },
    });

    if (!user) {
      throw new NotFoundException();
    }

    delete user.password;
    return user;
  }
}
```

Let's break this down:

- We added the `PrismaService` as a constructor parameter to let the framework inject an instance of it on application startup. I called it `db` for brevity since we are going to use it a lot.
- Instead of declaring our own user type, we used the `User` type generated by Prisma as the function return type to avoid code repetitions.
- If a user with the provided username does not exist, we simply throw a `NotFoundException` provided by Nest, which will be caught by the framework and result in an HTTP 404 error ( more on this feature in the official Nest documentation at [this page](https://docs.nestjs.com/exception-filters) ).
- Finally, we do not want to send to the client the user's password, therefore we need to remove it from the `user` object.

Let's now move on to the `create` method.

There is one important thing to consider here: we do not want to store users' passwords in plain text in the database.
We want to make things very difficult for anyone who manage to access our data, and that's exactly what **hashing functions**, and specifically the [bcrypt](https://github.com/kelektiv/node.bcrypt.js) library, are made for.
To better understand how does bcrypt work and how it manages to keep our passwords safe you can read [this](https://auth0.com/blog/hashing-in-action-understanding-bcrypt/) article.

What you need to know right now is that we'll use **bcrypt** to produce an **hashed** string which we'll store in the database instead of the password.
In the same way, when a user tries to log in, we need to compare the password he'll send to the server with the stored hash using the same library.

Let's install bcrypt and its types, and then use it to implement our `create` method.

```bash
npm install bcrypt
npm install @types/bcrypt --save-dev
```

```typescript
// users.service.ts

import {
  // ...
  ConflictException,
} from '@nestjs/common';
import { User, UserCreateInput } from '@prisma/client';
import { PrismaService } from '../prisma.service';
import bcrypt from 'bcrypt';

@Injectable()
export class UsersService {
  // ...

  async create(data: UserCreateInput): Promise<User> {
    const existing = await this.db.user.findOne({
      where: { username: data.username },
    });

    if (existing) {
      throw new ConflictException('username_already_exists');
    }

    // the second argument ( 10 ) is just a "cost factor".
    // the higher the cost factor, the more difficult is brute-forcing
    const hashedPassword = await bcrypt.hash(data.password, 10);

    const user = await this.db.user.create({
      data: {
        ...data,
        password: hashedPassword,
      },
    });

    delete user.password;
    return user;
  }
}
```

A few things to notice here:

- We used the `UserCreateInput` generated by Prisma as the argument type.
- We need to check if a user with the provided username exists, and if that's the case we throw a `ConflictException`, which corresponds to the 409 HTTP status code.
- As well as for the `findOne` method, we need to remove the password from the user object to avoid to send it to the client.

We can now use these methods in our controller and implement endpoints mapping.

To handle incoming data in the `POST /create` request body we need to declare a [DTO](https://en.wikipedia.org/wiki/Data_transfer_object) class, which will live in the `users/users.dto.ts` file.

```typescript
// users/users.dto.ts

export class CreateUserDto {
  username: string;
  password: string;
  displayName: string;
}
```

```typescript
import { Body, Controller, Get, Post, Param } from '@nestjs/common';
import { User } from '@prisma/client';
import { CreateUserDto } from './users.dto';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private service: UsersService) {}

  @Get(':username')
  findOne(@Param('username') username: string): Promise<User> {
    return this.service.findOne(username);
  }

  @Post()
  create(@Body() data: CreateUserDto): Promise<User> {
    return this.service.create(data);
  }
}
```

Let's see what we did here:

- The `Controller` decorator has one string parameter, `users`, which means that every endpoint in this controller will have a `users` base path.
- The `Get` decorator on top of the `findOne` method has a `:username` parameter. That means this method will handle every GET request to a path that includes some dynamic part after the `users/` prefix, such as `users/jack` or `users/xyz`. The dynamic part can be accessed in the method using the `Param` decorator.
- The `create` method uses the `Post` decorator because it is supposed to handle only POST requests. It also uses the `Body` decorator to inject the request body into the `data` parameter the same way we injected the `username` parameter in the `findOne` method with the `Param` decorator. The type of the `data` parameter is, of course, our `CreateUserDto` class.

There are some pretty evident security flaws in this implementation. The first one is that a user might send a POST request to create a user with invalid data, maybe an empty `username` or an empty object.

To fix these we can take advantage of a powerful feature Nest provides us: [pipes](https://docs.nestjs.com/pipes).

Pipes are simply classes that operate on the arguments of a controller's methods before they get passed to the handler function.

Data validation is the most typical use case for pipes, that's why Nest provides a built-in `ValidationPipe`, which we can use to validate our data along with the [`class-validator`](https://github.com/typestack/class-validator) and [`class-transformer`](https://github.com/typestack/class-transformer) libraries. Let's install them.

```bash
npm install class-transformer class-validator
```

Next, we need to set up the `ValidationPipe` in the `main.ts` file.

```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // validation pipe setup
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      whitelist: true,
      forbidNonWhitelisted: true,
    })
  );

  await app.listen(3000);
}
bootstrap();
```

We use the `app.useGlobalPipes` method to essentially tell Nest to validate incoming data for every request, with the following options:

- `transform: true` tells the pipe to transform every data field to a value of the desired type. This way even if a `string` field is sent as a number it will always be a `string`.
- `whitelist: true` and `forbidNonWhitelisted: true` tell the pipe to throw an HTTP 400 error ( Bad Request ) if there are any fields in the request body which are not specified in the DTO class.

To instruct our `ValidationPipe` on how to validate our `CreateUserDto` data fields we are going to use some decorators provided by the class-validator library.

```typescript
import { IsString, Length } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @Length(3, 30)
  username: string;

  @IsString()
  @Length(6, 30)
  password: string;

  @IsString()
  @Length(1, 50)
  displayName: string;
}
```

As simple as it looks, we want every field to be of type `string` and to respect some length constraints.

Our implementation is now complete, let's test this out:

```bash
http POST localhost:3000/users unknownField="xyz"
```

```json
HTTP/1.1 400 Bad Request

{
  "error": "Bad Request",
  "message": [
    "property unknownField should not exist",
    "username must be longer than or equal to 6 characters",
    "username must be a string",
    "password must be longer than or equal to 6 characters",
    "password must be a string",
    "displayName must be longer than or equal to 1 characters",
    "displayName must be a string"
  ],
  "statusCode": 400
}
```

```bash
http POST localhost:3000/users username="jack" password="123456" displayName="Jack"
```

```json
HTTP/1.1 201 Created

{
  "displayName": "Jack",
  "password": "123456",
  "username": "jack"
}
```

```bash
http localhost:3000/users/jack
```

```json
HTTP/1.1 200 OK

{
  "displayName": "Jack",
  "password": "123456",
  "username": "jack"
}
```

Looks like everything works as expected.

In the next part of this tutorial we'll take care of a crucial aspect of every web application: **authentication**.