Personal Project Overview — Backend, Part 1

Martin Mička
6 min readMar 31, 2023
Photo by Anthony DELANOIX on Unsplash

In my previous post, I wrote about a personal project of mine and briefly described its architecture and used technologies. Today, I will dive deeper into the backend part.

Node, TypeScript, and Nest

As previously mentioned, my application is split into two parts: the frontend and backend. The frontend is a Next.js (TypeScript with React) app, while the backend is a Node (TypeScript with Nest framework) app.

While I was already familiar with Node and a few frameworks, I wanted to dive deeper than just taking a few courses. When I worked with Node, Express was my weapon of choice. I appreciated its unopinionated and lightweight nature, although there were times when I preferred more opinionated tools (like Symfony or Laravel in the PHP ecosystem). After reading a lot of positive feedback on the internet and in various newsletters about Nest framework, I decided to give it a try.

Nest is a well-rounded, mature framework that offers more opinionated choices while remaining very flexible. For example, you are free to choose an ORM that you prefer or even adapt something entirely different as a data layer of your application. The same goes for the HTTP layer: Nest works by default with Express (via adapter), or you can choose Fastify (or any other library). This feature is powerful because it allows us to use extensions/tools built upon these well-known frameworks as well.

Nest also comes with full support for TypeScript, as it is itself written in it. As someone with a background in OOP and a liking for strongly typed languages (I’m looking at you, Kotlin), TypeScript was a no-brainer choice. I’m glad that new libraries in Node (or any other runtime) are built with TypeScript in mind.

Architecture

When designing the architecture for my application, I kept a few concepts in mind. First, I utilized the Nest framework, which is an MVC framework. Second, I wanted to separate my code into domains, as I see the appeal and value in this approach (I’ve also published a post on my thoughts on Evan’s “classic” book Domain Driven Design before).

My application is structured into domains, although there is not much logic to split at this point. One challenge that I faced was separating the “Presentation” layer, which includes Routes/Controllers and Views/Responses. These components tend to have a different scoping of data and require cross-domain orchestration. To avoid merging the highest level with my logical domains in the system, I created a domain specifically for the Presentation layer and called it simply “Presentation.” This domain is made up of smaller sub-domains, which represent logical scoping of the whole Presentation layer.

This approach is a tradeoff that gives me flexibility to scope routes and data orchestration while completely shielding the “core logic” from it. On the other hand, it creates another level of separation between layers. In simple cases, this might not be necessary, and more generic approaches will suffice. However, there may be cases where this comes in handy. For example, if the application had a frontend (being a conventional MVC application), this approach can create a nice boundary between the “true backend” — business and data logic split into domains — and the “soft backend” — routing and data orchestration, that can be tailored more towards the frontend.

This is where Nest as a framework shines. It’s built with microservices (and even domains) in mind, and gives us an abstraction called Module. Each domain (or any logical part of the app) can have its own Module, which declares what classes (Nest is tailored to use TypeScript/JavaScript classes) the Module imports (is dependent on), exports (what can be used as dependency in other Modules), and provides (what the module internally uses). This gives us fine-grained control over each part. Modules are also a part of Dependency Injection in Nest.

Module declaration in Nest application

Presentation Layer

I have made the backend part of my application completely headless and stateless. The only way to interact with it is through a REST API. The backend app also includes an authentication/authorization logic. It issues a JSON Web Token (JWT) against valid user credentials (email and password), which needs to be provided in the Authorization header throughout the application. The entry point to the application is the Controller.

Controller in Nest application

There’s a lot going on here, but bear with me. I’m going to break it down. First things first, Nest heavily relies on TypeScript decorators. I don’t mind this approach; I find it neat because it helps keep the code organized and it works well.

Base controller decorators and DI

The first part of this code involves a decorator that marks it as a Controller. This tells Nest to scan for routes and add them to the router. Additionally, controllers must be defined in a module. Another important aspect is the UseGuard decorator. This tells the framework that every route in the controller must be authorized by this guard. The guard functions as middleware, executing before the controller method. If the guard returns false, the request won’t proceed any further. It’s also possible to use a method-level UseGuard attribute. In that case, the class guard is executed first, followed by the method-level guard. Finally, it’s worth noting that this controller uses dependency injection via the class constructor, which is not typical in a Node application but can be useful.

Path declaration in Controller

This is what a method declaration looks like. In this case, the method is bound to the path /groups/:groupId. Here, /:groupId is a route parameter, and /group is a controller prefix. Additionally, there’s a Guard that validates whether the group ID is a valid Object ID (used by Mongo, for example). The Guard itself is shown below.

Next, there’s the actual request. The bound parameter comes from the path, and the body is serialized into an object. DTOs bound to methods in the Controller can hold validation rules from class-validator, which are automatically invoked upon parsing. Validation is handled before it reaches the controller method. I like this practice because it separates concerns well and helps with code reusability and even testing. DTOs can be simple as well, as demonstrated below.

ObjectID Guard
DTO with validation

Nest’s maturity and flexibility can be observed in every layer of the application. Responses (and exceptions) are serialized into JSON and returned with the appropriate status code. This behavior can also be customized. For example, we can change and return a different payload when processing a certain exception.

In my next post, we’ll take a look at business logic and data layer of my app. If you want to see the whole app, it’s on my Github: https://github.com/Mareddie/quizae.

--

--

Martin Mička

I’m 27 years old, working as a Software Developer, mainly interested in new technologies and psychology.