Javascript is required
ยท
8 min read

NestJS - The missing piece to easily develop full-stack TypeScript web applications

NestJS - The missing piece to easily develop full-stack TypeScript web applications Image

I think we all know this problem: You need to develop a new web application, and therefore, you need to implement the application's core in the backend and frontend. Both can be time-consuming, and if you make the wrong architectural decisions in the beginning, they can hardly be maintainable over time.

Additionally, projects are often started with a small core team. So it is essential that the team provides a solid architecture and can provide a good prototype on time and on budget. I, therefore, believe that it could make sense to start with a full stack TypeScript approach where you use Angular in the frontend and NestJS in the backend.

In this article, I will tell you about NestJS (from now on called Nest) and why I think such a full stack TypeScript web application could be a good tech stack choice for web apps.

Why should I use TypeScript in the backend?

In my opinion, this only makes sense in these scenarios:

  1. You have a small core team with good TypeScript knowledge, and this tech stack could fit the needs of your project now and in the future.
  2. In a project with multiple microservices, you have a specific microservice that serves as a backend for the frontend and is maintained by the frontend team.

If you have an existing backend team that is productive and happy with their tech stack, at least in my opinion, there is no need to use web technologies in the backend. But be at least open to the discussion, and maybe it could also fit your project.

What is Nest?

Nest website

The official description on the website is:

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

Under the hood, Nest makes use of robust HTTP Server frameworks like Express (the default) and Fastify. Nest provides a level of abstraction above these frameworks, but can also expose their APIs directly to the developer. This allows for easy use of the myriad third-party modules which are available for each platform.

It uses well-known frameworks like Node.js and Express and acts as a layer above. But why is this so special?

In my opinion, it is so brilliant due to these facts:

  • It is completely written in TypeScript, and you can also implement your applications out-of-the-box in TypeScript
  • Provides an out-of-the-box application architecture that was deeply inspired by Angular

But even if Angular inspired it, Nest is a separate project and independent of the Angular project itself. You can build a front-end-agnostic API that can be used with other frameworks and libraries like React, Vue.js, etc.

TypeScript

Nest website

Nest applications are written in TypeScript, but you can also choose to write them in JavaScript (which I would not recommend). TypeScript is a superset of JavaScript and provides the flexibility of JavaScript with the safety and robustness of a typed language.

TypeScript is compiled to JavaScript, so the compiler can already catch possible runtime errors during compilation. The language calls itself "JavaScript that scales" and Nest also wants to provide a scalable backend architecture, so I think this was a good choice.

Additionally, I would recommend using TypeScript in the frontend framework you are currently using. Angular provides TypeScript out-of-the-box, but you can, of course, also use it in other popular frameworks like React or Vue.

This way, you have a common language in your frontend & backend code where you can share your type definitions. This approach heavily reduces the efficiency losses by context switches from one language to another and can increase your team performance in general.

Nest Architecture

Nest modules

As already mentioned, Nest's architecture was deeply inspired by Angular.

Usually, server-side JavaScript frameworks are more focused on flexibility than on providing a scalable architecture for your code. Usually, you need to invest time by yourself to arrange the code clearly and define guidelines for it.

Nest provides a lovely architecture out of the box, which we will now analyze in more detail. It consists of modules, controllers, and services.

File structure

A Nest project has a similar file structure to an Angular project:

1.
2|-- app.controller.spec.ts
3|-- app.controller.ts
4|-- app.module.ts
5|-- app.service.ts
6|-- main.ts
7|-- news
8    |-- news.controller.spec.ts
9    |-- news.controller.ts
10    |-- news.module.ts
11    |-- news.service.spec.ts
12    |-- news.service.ts

Dependency Injection

Nest is built like Angular around the design pattern Dependency Injection. You find an excellent article about this pattern in the official Angular documentation.

Let us look at a simple example:

If we need an instance of another class, we define it in the constructor:

constructor(private readonly newsService: NewsService) {}

Nest will create and resolve an instance of NewsService. In the typical case of a singleton, it will return the existing instance if it has already been requested. Every class that can be injected needs to declare the @Injectable annotation, as you can see later in the service section.

Modules

A module is the basic building block of each Nest application and groups related features like services and controllers. If you create a new Nest application, you have the AppModule automatically available.

In theory, you could write your whole application in one module, but in most cases, this is not the correct approach. It is recommended to group each of your features in a module, for example, a NewsModule and a UserModule.

A simple module example:

1@Module({
2  controllers: [NewsController],
3  providers: [NewsService],
4})
5export class NewsModule {}

Angular uses the same concept of modules, and you even define them the same way in your code.

Controllers

In Nest, you use annotations to define your controllers like it is done in frameworks like Spring Boot. Controllers are responsible for handling incoming requests and returning responses to the client.

You decorate your controller class with the required @Controller decorator, which you can pass a path as the primary route for this controller. Each method inside your controller class can be annotated by common decorators like @Get, @Post, @Put, and @Delete.

1@Controller('news')
2export class NewsController {
3  @Get()
4  findAll(): string {
5    return 'This action returns all news'
6  }
7}

As we did not add path information to our @Get decorator of the findAll method, Nest will map GET /news requests to this handler.

Services

Services are used in Nest to keep your controllers slim and encapsulate the logic.

1@Injectable()
2export class NewsService {
3  private readonly news: News[] = [{ title: 'My first news' }]
4
5  create(news: News) {
6    this.news.push(news)
7  }
8
9  findAll(): News[] {
10    return this.news
11  }
12}

Now we can use this service in our controller:

1@Controller('news')
2export class NewsController {
3  constructor(private readonly newsService: NewsService) {}
4
5  @Get()
6  async findAll(): Promise<News[]> {
7    return this.newsService.findAll()
8  }
9}

Testing

Testing meme

Nest provides us with a setup for unit, integration, and end-to-end tests.

Unit Tests

Jest is used as a unit test framework in Nest. If you also use the same test framework in the frontend this can be very beneficial and improve your team's performance.

A simple unit test for our NewsService:

1describe('NewsService', () => {
2  let service: NewsService
3
4  beforeEach(async () => {
5    const module: TestingModule = await Test.createTestingModule({
6      providers: [NewsService],
7    }).compile()
8
9    service = module.get<NewsService>(NewsService)
10  })
11
12  it('should be defined', () =>