Javascript is required
·
8 min read
·
3450 views

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', () => {
13    expect(service).toBeDefined()
14  })
15
16  it('should return first news article', () => {
17    expect(service.findAll()).toEqual([{ title: 'My first news' }])
18  })
19})

Thanks to dependency injection it is also very easy to mock services, e.g. in your controller tests:

1describe('News Controller', () => {
2  let controller: NewsController
3
4  const testNews = {
5    title: 'Test News',
6  }
7
8  class NewsServiceMock {
9    public findAll() {
10      return [testNews]
11    }
12  }
13
14  beforeEach(async () => {
15    const module: TestingModule = await Test.createTestingModule({
16      controllers: [NewsController],
17      providers: [{ provide: NewsService, useClass: NewsServiceMock }],
18    }).compile()
19
20    controller = module.get<NewsController>(NewsController)
21  })
22
23  it('should be defined', () => {
24    expect(controller).toBeDefined()
25  })
26
27  it('should get news', () => {
28    expect(controller.findAll()).toEqual([testNews])
29  })
30})

End-to-end test

With end-to-end tests, you test the complete functionality of your API and not just one particular function.

Nest uses Supertest which can be used to simulate HTTP requests:

1describe('News Controller (e2e)', () => {
2  let app
3
4  beforeEach(async () => {
5    const module: TestingModule = await Test.createTestingModule({
6      imports: [NewsModule],
7    }).compile()
8
9    app = module.createNestApplication()
10    await app.init()
11  })
12
13  it('/ (GET)', () => {
14    return request(app.getHttpServer())
15      .get('/news')
16      .expect(200)
17      .expect([{ title: 'My first news' }])
18  })
19})

Swagger

If you develop a public API, you may need to use OpenAPI (Swagger) specification to describe RESTful APIs. Nest provides a module to integrate it.

We only need to install the swagger package @nestjs/swagger and add a few lines to our main.ts.

1async function bootstrap() {
2  const app = await NestFactory.create(AppModule)
3
4  const options = new DocumentBuilder()
5    .setTitle('News example')
6    .setDescription('The news API description')
7    .setVersion('1.0')
8    .addTag('news')
9    .build()
10  const document = SwaggerModule.createDocument(app, options)
11  SwaggerModule.setup('api', app, document)
12
13  await app.listen(3000)
14}
15bootstrap()

In our main.tsfile, we specified that the API doc is available under /api. So if we open a browser and navigate to http://localhost:3000/api we get the following API doc.

NestJS Swagger

Getting Started

I can highly recommend the official Nest documentation as a starting point.

Nest provides the Nest CLI, a lovely command-line interface tool that helps you initialize and develop your applications. You can scaffold a new project or add new services, components, and more as you know it from the Angular CLI.

You just need these three commands to run the project locally at http://localhost:3000/:

bash
1$ npm i -g @nestjs/cli
2$ nest new project-name
3$ npm run start

The Future of Nest

Nest has currently more than 14k stars on GitHub and more than 100k weekly downloads on npm. It is already used by many companies in production with Adidas as the biggest user.

Nest is unique as a server-side JavaScript framework due to its use of TypeScript and relation to Angular, but it is missing the support of a large enterprise. This is just a minor concern but should be considered if you choose a framework for your tech stack.

Conclusion

Nest fits perfectly into a full-stack Typescript web application, especially if you choose Angular in the frontend.

The folder structure and design patterns in Nest are heavily based on Angular. Due to this simple and similar structure, we as developers can focus more on designing the endpoints instead of wasting time on application structuring. Nest hides many nasty Node.js and ugly JavaScript boilerplate behind annotations, structures, and patterns. Thanks to TypeScript as a backend language, going from your frontend to backend code is less painful and without fewer context switches.

I will never share any of your personal data. You can unsubscribe at any time.

If you found this article helpful.You will love these ones as well.
How I Replaced Revue With a Custom-Built Newsletter Service Using Nuxt 3, Supabase, Serverless, and Amazon SES Image

How I Replaced Revue With a Custom-Built Newsletter Service Using Nuxt 3, Supabase, Serverless, and Amazon SES

Create an RSS Feed With Nuxt 3 and Nuxt Content v2 Image

Create an RSS Feed With Nuxt 3 and Nuxt Content v2

The 10 Favorite Features of My Developer Portfolio Website Image

The 10 Favorite Features of My Developer Portfolio Website

Track Twitter Follower Growth Over Time Using A Serverless Node.js API on AWS Amplify Image

Track Twitter Follower Growth Over Time Using A Serverless Node.js API on AWS Amplify