Skip to content

Reducing Mental Fatigue: NestJS + ObjectionJS

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

▶️ Introduction

Most of the article you’ll find me ranting on what helps me enjoy my job.

But you’ll also figure out how to start using Objection with Nest and what they are all about. I won’t explain in detail how Nest or Objection work. I think those two have wonderful documentation which is fun and worth being explored, BUT I’ll show you how one can start using them together 😌

☝️ Prerequisites

You need to have PostgreSQL available locally. This can be achieved in various ways but I would suggest two approaches:

  1. You can install docker [Windows, macOS, Ubuntu] and execute a package.json script described later in the article . Which essentially runs PostgreSQL in a docker container and destroys it once you hit ctrl+c in your terminal

  2. You can install it directly on your machine (though I’d recommend option #1 if you don’t have it already installed)

💆 Mental fatigue

Lot's of programmers nowadays are dealing with mental fatigue in software development. And it's not only because of new tools, approaches, and paradigms that pop up every minute.

With every new software development project (let's assume back-end app, cause I'm more of a back-end person) a programmer or a team should decide on a variety of things:

  • Web framework
  • Project structure
  • Linting rules, code formatting (based on Google, Airbnb, Microsoft or homegrown conventions)
  • Data storage (SQL, NoSQL)
  • Deployment platform (Amazon, Google Cloud, Azure, Netlify, etc.)
  • CI/CD
  • Testing tools and strategies
  • Documentation
  • And so on and so forth ...

As you can see this list can grow on and on. A whole lot of high-level decisions involved in this process, not talking about millions of small ones.

Every decision made depletes our mental energy even if it's trivial. After lots of minor decisions, we are less capable to make a good major one.

A tools landscape that we have right now in a JavaScript world is a blessing and a curse at the same time. On one side, it causes us to make more decisions, on the other side there are gems that come with lot's of good decisions made for us, decisions trusted by thousands of developers.

So why can't we use this opportunity to reduce our mental fatigue, reduce the number of decisions we make by adopting well-proven opinions?

By choosing the right tools we have a chance to develop a project which is easy to reason about and onboard new people on.

Almost any back-end project gets built on top of a web framework and ORM of some kind that heavily influence future project architecture.

We’ll have a look at the way they can simplify mental models we built in mind and reduce the number of decisions we usually have to make. We are going to develop a simple (but enough to demonstrate the powers of Nest and Objection) back-end for a note-taking app.

🕸️ Web framework

There are plenty of web frameworks available in the Node.js land: Express, Hapi, Koa, Fastify, Restify, etc. They are flexible and time-tested folks that allow you to structure a project in many different ways.

So you need to decide on how you want to organize routes, handlers, views, authentication, services, repositories, etc. This gives you a lot of freedom but comes with a cost. You need to make plenty of decisions to properly architect the app, and the way project is organized will be different in any other project that gets built using the same framework, because developers of a new project made their decisions in a slightly different way.

You have to start over again and grasp the way this framework is used in that particular project. You’re loosing that feeling of familiarity and awareness you developed on the previous project, or in other words, the level of framework knowledge conversion is not that high.

For me personally, those frameworks are missing one important thing (though I think they are very powerful) - a shared conceptual base, on top of which you can start growing an actual business logic, this base would repeat from project to project and allow to quickly familiarize new developers with the codebase.

Such conceptual base allows the increase of the framework’s knowledge conversion and reduce the amount of mental effort needed to start using it.

What do I mean by conceptual base? It is a minimal set of concepts or building blocks that framework gives you. And if those building blocks get aligned well with what you need to develop, it becomes easy to reason about the project and to communicate its different parts to other team members (to both seasoned developers and newcomers).

For me, such a framework is Nest! It’s written in Typescript and has good, concise documentation. So for those who don’t like to read lengthy manuals (I don’t), this documentation gives just enough information and examples to do the job - no more, no less.

Nest has modules systems heavily inspired by Angular, so Angular developers should be quite comfortable reading Nest code. Angular and Nest is usually a good combination because their conceptual bases have a high intersection, and you can transfer some of your Angular knowledge to Nest.

I don’t want to repeat the docs, and encourage you to have a look on your own.

Though I’ll describe Nest’s main building blocks:

  • Guard - protects system from unauthenticated/unauthorized access
  • Interceptor - intercepts incoming requests or outgoing responses
  • Controller - processes the requests
  • Provider - this is basically a service that is dedicated to some set of tasks and can be injected into any other thing from this list thanks to Nest built-in dependency injection capabilities
  • Pipe - transforms/validates incoming request body
  • Middleware - the purpose of the middleware is to intercept the request, execute some logic and pass the control flow to the next middleware
  • Module - this thing helps to organize your application structure and it has the same purpose as Angular modules do

Also, I suggest reading on this series of articles on Nest.js Step by Step.

You can ask me “Why do I need other stuff listed above if I have middlewares?”.

Middlewares are too generic whereas Guards, Intercepts, etc. are dedicated to one particular task. So by hearing the word “Guard”, you already know what it is responsible for.

You might need middlewares if you want to implement something beyond those concepts listed here.

💥 Big Bang! For the sake of brevity, I won’t be describing every file of our future project, but rather will be highlighting key concepts along the way.

We’re starting with the Nest application, which has all the plumbings but no database. This way it’ll be easy to talk about Nest stuff and then gradually move to Objection. As I already said we are going to develop a toy notes API. Our notes can have theme and tags.

This is what our app structure looks like initially:

Initial app structure

Go ahead and investigate the code we have so far (the codebase is on the initial commit at the moment). We're gonna start building on top of it.

Just by looking at names we have in the codebase it becomes immediately clear the purpose and responsibilities of different classes.

Let’s have a look at notes folder in more details (cause tags and themes works exactly in the same vein).

The first thing is NotesModule

In NotesModule we’ve registered NotesService. Here how it looks

NotesService is used by NotesController and is injected by Nest once it discovers that the latter is dependent on the former.

You might have noticed that NotesService (as well as other services) is just a stub at the moment and does nothing. We’re going to fix that soon, after a small conversation about ORMs.

🦖 ORM

Historically, the purpose of ORMs was to remove object-relational impedance mismatch. They do this by abstracting out RDBMS and relational concepts as much as possible, they are especially good at hiding SQL from you and forcing you to use their DSL, which still sucks because it is a prominent example of a leaky abstraction.

I remember lot’s of situations when I was struggling with such DSL for hours, trying to mimic a query which I’d already written in SQL (and spent minutes on this). Even if we’ve managed to write a proper DSL it still might be converted into monstrous (not always performant) SQL you have no control over.

The true power of relational databases comes with SQL and its declarative expressiveness. In reality, the majority of my colleagues are quite good with the RDBMS concepts. It’s comfortable for them to think in terms of SQL queries and more often than not developers have an intuitive understanding of how DB record should be represented as an object (dictionary, map, you name it) in their language of choice.

It simply makes no sense to hide SQL from developers in ORMs because you still have to know it for fetching at least something from the DB, but apart from that you need to enable a compiler in your head that converts DSL to SQL in order to understand what kind of query will be generated eventually and whether it’ll give you what you want.

It’s double work that puts extra pressure on your brain which is trying to keep and reconcile a million of other little things about the project you are working on.

If you are already proficient with SQL, why do you need to learn another language (DSL) to fetch/update stuff from/in the database? Wouldn’t it be better for ORMs to implement an API that is as close to SQL as possible, allowing to transfer existing developer’s SQL knowledge to that API and flattening learning curve? Such API would take advantage over the language features like auto-completion and static code analysis while still being close to generated SQL.

The solutions like Hibernate, TypeORM and similar ones are overloaded, heavyweight and over-complicated in my opinion.

And here is where Objection comes in. Comparing to other ORMs it doesn’t try to put SQL and relational model behind the curtains. Here is how Objection developers describe their product:

Objection.js is an ORM for Node.js that aims to stay out of your way and make it as easy as possible to use the full power of SQL and the underlying database engine while still making the common stuff easy and enjoyable.

🍽️ Integrating Objection with Nest

TLDR; If you just need to know what should be done to have Objection support in Nest, here is the diff which shows changes that should be applied on top of our initial commit.

1️⃣ Installing required dependencies npm i @types/dotenv dotenv objection knex pg

  • dotenv populates process.env with environment variables defined in the .env file
  • objection - the ORM
  • knex is a SQL query builder that Objection uses under the hood. It also provides migrations and data-seeding support (we’ll talk about this a bit later)
  • pg is a client for PostgreSQL database.

2️⃣ Relational model Next step is to define our relation model (for now just get comfortable with the tables we are about to build)

Note app relational model
  • Notes might have a theme
  • Notes can have multiple tags
  • One tag can belong to multiple notes

knex_migrations and knex_migrations_lock are tables created and managed by Knex. They are not relevant for our data model.

3️⃣ Extending package.json with helper scripts Before we start creating the migrations, let’s add a couple of commands to our package.json No worries, their purpose will become clear in later sections.

4️⃣ Knexfile In the package.json excerpt above you might be noticed --knexfile knexfile.ts. This is an argument that points to Knex configuration file, so let’s create it at the root of the project.

knexSnakeCaseMappers converts camelCase names in code to snake_case names in the database. So in our database model, we have a themes table with font_family column. In order to update this column from the code, you can refer to it using fontFamily and mappers will do the job by transforming font_family → fontFamily and vice versa automatically.

The purpose of migrations is to create a database schema and subsequent changes to that schema that might come up over time. This allows versioning your database and rollback schema to its previous state when needed.

Seeds are useful in the development environment when you need to populate your database with some data.

migration.stub and seed.stub are template files which Knex uses to generate our migrations and seeds. Put those under the database folder as specified in the config

migration.stub
seed.stub

5️⃣ Migrations Now when we have knexfile.ts created we can start using Knex commands we’ve added previously to the package.json.

  • npm run migrate:make CreateTags
  • npm run migrate:make CreateThemes
  • npm run migrate:make CreateNotes
  • npm run migrate:make CreateNoteTags

Those will generate migration files under the database/migrations folder using our migration.stub.

It’s time to define our tables. Let’s do it together for CreateNotes migration for others please have a look at the final solution.

6️⃣ Connect our models with Objection In order to reflect relational tables in our code, we need to create a bunch of appropriate classes called models. For now, they are just plain Typescript classes located under database/models directory, so let’s sprinkle some Objection on them.

BaseModel ( base.model.ts )

TagModel ( tag.model.ts )

NoteTagModel ( note-tag.model.ts )

ThemeModel ( theme.model.ts )

7️⃣ Mapping relations Especially interesting for us is how Objection handles relations between tables and the way we can express them in code.

8️⃣ Connecting models to database and database.module.ts

Each model class can be used to perform various SQL queries, but for that, we need to wire those classes with a Knex database connection.

Once they are wired we can expose those classes as injectable service to other modules.

DatabaseModule needs to be registered under the main ApplicationModule, so all its exported services are available to other modules.

9️⃣ Implementing.service.ts files To start manipulating data we have in the database, we need to implement methods defined in the .service.ts files.

Each service relies on the model class(-es) we’ve exposed through the module’s exports above. Here is NotesService implementation (notes.service.ts):

As you can see the .query() method is a gateway for building rich queries. In the example above we also have a transaction example, so any error thrown in the transaction callback will cause database changes triggered inside of that callback to roll back.

🔟 Loading Note relations Let’s have a look at findOne method in NotesController:

The notable change is $loadRelated invocation. Here we’re asking Objection to load relations for this particular note:

tags and theme are names of the relations defined in the NoteModel class. This is how Objection knows how to fetch them.

All fetched relations get transformed into appropriate model instances. Once fetched, Objection will create tags and theme fields for this particular note instance.

So all the relations get loaded only on demand by default.

In case you want to fetch lots of objects with loaded relations there is another way you can use:

In here, once all notes are loaded - Objection will loads tags relation for all of them.

1️⃣ 1️⃣ Seeds Now we’re ready to generate seed files:

  • npm run seed:make 01-Tags
  • npm run seed:make 02-Themes
  • npm run seed:make 03-Notes
  • npm run seed:make 04-NoteTags

Seeds get generated under the database/seeds folder using our seed.stub.

Seed files get executed by Knex in order, so we have to ensure that it is correct. This is the reason we’ve prefixed seed files with numbers: we want to have tags created before note-tags because the latter depends on the former.

Let’s have a look at 02-Themes.ts seed implementation

1️⃣ 2️⃣ dotenv dotenv is a library that loads environment variables from .env file into process.env We’re going to utilize it for defining DATABASE_URL env var, which then will be used throughout the app including migrations and seed scripts.

All you need is to

  1. create a .env file at the root of the app and put there this single line DATABASE_URL=postgres://postgres:docker@localhost:5432/postgres

This connection string gets constructed based on the command we have in package.json. Postgres uses postgres as a name for a default user and database.

  1. Add dotenv import at the very top of knexfile.ts and main.ts

1️⃣ 3️⃣ Running PostgreSQL At this point we need to start our PostgreSQL instance:

`npm run run:pg-docker`

Then create the schema (by executing migrations) and populate it with data (by executing seeds):

`npm run migrate && npm run seed`

🚀 Playing with the app

Now you should have a fully working Nest application with the Objection support.

You can run it using

`npm run start`

README.md contains example http requests (using curl) which you can modify and execute against the server.

And we’re done 🎉

✍️ Summary

In the article I shared my thoughts on mental fatigue and that with the right tools it can be reduced by utilizing clear and intuitive concepts that help to communicate and share knowledge with others.

Nest does this by providing a conceptual base, which is great not only for reasoning about the project but also for communicating the way it works to other developers.

Objection gives you a framework that allows thinking in SQL terms, and avoid wasting time debugging esoteric DSLs. I would call it the “ORM without a pain”.

I hope you’ve enjoyed the article and get some understanding of how to start using Objection with Nest.

You can find the full project on my github.

Working with AWS AppSync

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Introduction to RESTful APIs with NestJS cover image

Introduction to RESTful APIs with NestJS

Welcome to an introduction to RESTful APIs with NestJS. Understanding JavaScript and TypeScript will make it easier to follow the directions in this article, but you don't necessarily need to be proficient. NestJS is one of the most rapidly growing frameworks for building Node.js server-side applications. Companies such as *Roche*, *Adidas*, and *Autodesk*, all trust NestJS when it comes to building efficient and scalable server-side applications. NestJS is based heavily on *Angular*, and uses Angular-like modules, services, controllers, pipes, and decorators. This allows NestJS to help developers create scalable, testable, loosely coupled, and easily maintainable applications. NestJS was built with TypeScript, but it can also support pure JavaScript development. NestJS offers a level of abstraction above two very popular Node.js frameworks, either *Express.js* or *Fastify*. This means that all of the great middleware that are available for Express.js and Fastify can also be used with NestJS. The best way to get familiar with NestJS is to build a basic RESTful API with CRUD (Create, Read, Update, and Delete) functionality. This is exactly what we'll be doing together in this article. We'll be building a simple RESTful API for a Blog, with endpoints to handle CRUD operations on blog posts. Getting Started Code editor Using Visual Studio Code as a code editor can help speed up NestJS development because of its smart IntelliSense and great TypeScript support. In Visual Studio Code, make sure that you have the following user setting by going to File / Preferences / Settings, and searching for the user setting named typescript.preferences.importModuleSpecifier. Make sure to set it to relative as seen below. ` This will allow Visual Studio Code to use relative paths rather than absolute paths when auto-importing. Using absolute path imports in our application can lead to problems if and when our code ends up in a different directory. Insomnia Insomnia is a useful API testing tool that we will use to test the NestJS API that we will be building. The NestJS CLI To get started with NestJS, let's install the Nest CLI. The Nest CLI is a command-line interface tool that makes it easy to develop, and maintain NestJS applications. It allows us to run our application in development mode, and to build and bundle it for a production-ready release. ` Creating a new NestJS project With the Nest CLI now installed, we can use it to create a new project. ` This command will create a new project directory called rest-api. It will create a base structure for our project, and add in the following core Nest files: - app.controller.ts: A controller with a single route. - app.controller.spec.ts: The controller's unit tests. - app.module.ts: The root module of our application. - app.service.ts: A service for the AppModule's business logic. - main.ts: The entry file of our application. The initial project structure created by the Nest CLI encourages us to follow the common convention of keeping each module in its own directory. Testing the sample endpoint The installation of NestJS comes with a sample API endpoint that we can test by making a request to it. If we open app.controller.ts, we can see that there is a GET endpoint that was created for us with the @Get() decorator. It returns a 'Hello World!' string. ` Let's run npm run start:dev from our project folder. This will run our NestJS app in watch mode, which provides live-reload support when application files are changed. Once NestJS is running, let's open http://localhost:3000 in our web browser. We should see a blank page with the Hello World! greeting. We can also use API testing tools such as Insomnia to make a GET request to http://localhost:3000. We should get the same Hello World! greeting as our result. Let's remove this endpoint since it was only added by the Nest CLI for demo purposes. Go ahead and delete app.controller.ts, app.service.ts, and app.controller.spec.ts. Also, delete all references to AppController and AppService in app.module.ts. Creating a feature module The architectural design of NestJS encourages feature modules. This feature-based design groups the functionality of a single feature in one folder, registered in one module. This design simplifies the codebase and makes code-splitting very easy. Module We create modules in NestJS by decorating a class with the @Module decorator. Modules are needed to register controllers, services, and any other imported sub-modules. Imported sub-modules can have their own controllers, and services, registered. Let's use the Nest CLI to create the module for our blog posts. ` This gives us an empty PostsModule class in the posts.module.ts file. Interface We will use a TypeScript interface to define the structure of our JSON object that will represent a blog post. An interface is a virtual or abstract structure that only exists in TypeScript. Interfaces are used only for type-checking purposes by the TypeScript compiler. TypeScript interfaces do not produce any JavaScript code during the transpilation of TypeScript to JavaScript. Let's use the Nest CLI to create our interface. ` These commands allow us to create a posts.interface.ts file in the feature-based folder for our blog posts, which is /src/posts. The TypeScript interface keyword is used to define our interface. Let's make sure to prefix it with export to allow this interface to be used throughout our application ` From the command-line prompt, let's reset our current working directory back to the root folder of our project by using the following command. ` Service Services are classes that handle business logic. The PostsService that we will be creating will handle the business logic needed to manage our blog posts. Let's use the Nest CLI to create the service for our blog posts. ` This will give us an empty PostsService class in the posts.service.ts file. The @Injectable() decorator marks the PostsService class as a provider that we can register in the providers array of our PostsModule, and then inject into our controller class. More on this later. Controller Controllers are classes that handle incoming requests and return responses to the client. A controller can have more than one route or endpoint. Each route or endpoint can implement its own set of actions. The NestJS routing mechanism handles the routing of requests to the right controller. Let's use the Nest CLI to create the controller for our blog posts. ` This will give us an empty PostsController class in the posts.controller.ts file. Let's inject our PostsService into the constructor of the PostsController class. ` NestJS uses dependency injection to set up a reference to PostsService from within our controller. We can now use the methods provided by PostsService by referencing this.postsService from within our controller class. Registering our controller and service Let's make sure that our PostsModule registers our PostsController and PostsService. The nest generate service post and nest generate controller post commands that we ran earlier have automatically registered the PostsService and PostsController classes in the PostsModule for us. ` NestJS uses the term providers to refer to service classes, middleware, guards, and more. If the PostsService class needs to be made available to other modules within our application, we can export it from PostsModule by using the exports array. This way, any module importing the PostsModule will be able to use the PostsService. ` Importing PostsModule into AppModule We now have one cohesive and well-organized module for all the functionality related to our blog posts. However, all the functionality that will be provided by our PostsModule is not available to our application unless the AppModule imports it. If we revisit the AppModule, we can see that the PostsModule has been automatically added to the imports array by the nest generate module post command that we ran earlier. ` Adding Service and Controller logic Our PostsService and PostsController have no functionality implemented at the moment. Let's now implement our *CRUD* endpoints and their corresponding logic while adhering to RESTful standards. NestJS makes it easy to use MongoDB (using the @nestjs/mongoose package), or PostgreSQL (using *Prisma* or *TypeORM*) to persist and manage our application's data. However, for the sake of simplicity, let's create a local array in our service to mock a database. ` A service, or provider, as NestJS refers to them, can have its scope configured. By default, a service has a *singleton* scope. This means that a single instance of a service is shared across our entire application. The initialization of our service will occur only once during the startup of our application. The default singleton scope of services means that any class that injects the PostsService will share the same posts array data in memory. Get all posts Let's add a method to our PostsService that will return all of our blog posts. ` Let's add a method to our PostsController that will make the logic of the service's findAll() method available to client requests. ` The @Get decorator is used to create a GET /posts endpoint. The /posts path of the request comes from the @Controller('posts') decorator that was used in order to define our controller. Get one post Let's add a method to our PostsService that will return a specific blog post that a client may be looking for. If the id of the requested post is not found in our list of posts, let's return an appropriate 404 NOT FOUND HTTP error. ` Let's add a method to our PostsController that will make the logic of the service's findOne() method available to client requests. ` The @Get decorator is used with a parameter as @Get(':id') here to create a GET /post/[id] endpoint, where [id] is an identification number for a blog post. @Param, from the @nestjs/common package, is a decorator that makes route parameters available to us as properties in our method. @Param values are always of type string. Since we defined id to be of type number in TypeScript, we need to do a string to number conversion. NestJS provides a number of pipes that allow us to transform request parameters. Let's use the NestJS ParseIntPipe to convert the id to a number. Create a post Let's add a method to our PostsService that will create a new blog post, assign the next sequential id to it, and return it. If the title is already being used by an existing blog post, we will throw a 422 UNPROCESSABLE ENTITY HTTP error. ` Let's add a method to our PostsController that will make the logic of the service's create method available to client requests. ` The @Post decorator is used to create a POST /post endpoint. When we use the NestJS decorators for the POST, PUT, and PATCH HTTP verbs, the *HTTP body* is used to transfer data to the API, typically in JSON format. We can use the @Body decorator to parse the *HTTP body*. When this decorator is used, NestJS will run JSON.parse() on the *HTTP body* and provide us with a JSON object for our controller method. Within this decorator, we declare post to be of type Post because this is the data structure that we are expecting the client to provide for this request. Delete a post Let's add a method to our PostsService that will delete a blog post from our in-memory list of posts using the JavaScript splice() function. A 404 NOT FOUND HTTP error will be returned if the id of the requested post is not found in our list of posts. ` Let's add a method to our PostsController that will make the logic of the service's delete method available to client requests. ` Update a post Let's add a method to our PostsService that will find a blog post by id, update it with newly submitted data, and return the updated post. A 404 NOT FOUND HTTP error will be returned if the id of the requested post is not found in our list of posts. A 422 UNPROCESSABLE ENTITY HTTP error will be returned if the title is being used for another blog post. ` Let's add a method to our PostsController that will make the logic of the service's update method available to client requests. ` We use the @Put decorator to make use of the HTTP PUT request method. PUT can be used to both create and update the state of a resource on the server. If we know that a resource already exists, PUT will replace the state of that resource on the server. Testing our feature module Let's start our development server using npm run start:dev. Then, let's open the Insomnia application to test the API endpoints that we created for the PostsModule. Get all posts Let's make a GET request to http://localhost:3000/posts. The result should be a 200 OK success code with an empty array. Create a post Let's make a POST request to http://localhost:3000/posts using the following as our JSON body. ` We should get a successful 201 Created response code, along with a JSON representation of the post that was created, including the id field that has been automatically generated. Get a post Let's make a GET request to http://localhost:3000/posts/1. The result should be a successful 200 OK response code, along with the data for the post with an id of 1. Update a post Let's make a PUT request to http://localhost:3000/posts/1 using the following as our JSON body. ` The result should be a successful 200 OK response code, along with a JSON representation of the post that was updated. Delete a post Let's make a DELETE request to http://localhost:3000/posts/1. The result should be a successful 200 OK response code with no JSON object returned. Logging NestJS makes it easy to add logging to our application. Rather than using console.log() statements, we should use the Logger class provided by NestJS. This will provide us with nicely formatted log messages in the Terminal. Let's add logging to our API. The first step is to define the logger in our service class. ` With the logger now defined, we can add log statements in our service class. Here is an example of a log statement that we can add as the first line of the findAll() method in the PostsService class. ` Such statements provide us with a convenient log message in the Terminal every time a service method is called during a client's request to our API. When a GET /posts request is sent, we should see the following message in the Terminal. ` Swagger NestJS makes it easy to add the *OpenAPI* specification to our API using the NestJS *Swagger* package. The OpenAPI specification is used to describe RESTful APIs for documentation and reference purposes. Swagger setup Let's install Swagger for NestJS. ` Swagger configuration Let's update the main.ts file that bootstraps our NestJS application by adding Swagger configuration to it. ` With our NestJS app running, we can now go to http://localhost:3000/api to view the Swagger documentation for our API. Notice that default is displayed above the routes for our posts. Let's change that by adding @ApiTags('posts') right below @Controller('posts') in our PostsController class. This will replace default with posts to indicate that this set of endpoints belongs to the posts feature model. ` ApiProperty Let's make the properties of our PostModel interface visible to Swagger. We do so by annotating fields with the ApiProperty() decorator, or the ApiPropertyOptional() decorator for optional fields. To use these decorators, we must first change our interface to a class in the posts.interface.ts file. ` Within the ApiProperty() decorator applied to each field, we describe the field type. The id field is optional since we don't know of a new blog post's id when creating it. A date-time string format is used for the date field. These changes allow the PostModel structure to be documented within Swagger. With our NestJS app running, we can now go to http://localhost:3000/api to view the documentation of the PostModel. ApiResponse Let's use the @ApiResponse() decorator for Swagger to document all the possible responses from our API's endpoints. This will be helpful for informing users of our API what responses they can expect to receive by calling a given endpoint. We will be making these changes in the PostsController class. For the findAll method, let's use the @ApiOkResponse() decorator to document a 200 OK success response. ` For the findOne method, let's use the @ApiOkResponse() decorator to document a 200 OK response when the post is found. Let's use the @ApiNotFoundResponse() decorator to document a 404 NOT FOUND HTTP error when a post is not found. ` For the create method, let's use the @ApiCreatedResponse() decorator to document a 201 CREATED response when a post is created. Let's use the @ApiUnprocessableEntityResponse() decorator to document a 422 UNPROCESSABLE ENTITY HTTP error when a duplicate post title is found. ` For the delete method, let's use the @ApiOkResponse() decorator to document a 200 OK response when a post is deleted. Let's use the @ApiNotFoundResponse() decorator to document a 404 NOT FOUND HTTP error when the post to be deleted is not found. ` For the update method, let's use the @ApiOkResponse() decorator to document a 200 OK response when a post is updated. Let's use the @ApiNotFoundResponse() decorator to document a 404 NOT FOUND HTTP error when the post to be updated is not found. Let's use the @ApiUnprocessableEntityResponse() decorator to document a 422 UNPROCESSABLE ENTITY HTTP error when a duplicate post title is found. ` After saving these changes, all response codes and their related descriptions should now be listed for each endpoint on the Swagger web page at http://localhost:3000/api. We could also use Swagger instead of Insomnia to test our API. By clicking on the "Try it out" button that appears under each endpoint on the Swagger web page, we can see if they are working as expected. Exception filter Exception filters give us full control over the exceptions layer of NestJS. We can use an exception filter to add custom fields to a HTTP exception response body or to print out logs of every HTTP exception that occurs to the Terminal. Let's create a new filters folder in the /src folder, as well as a new file called http-exception.filter.ts within it. Let's add the following class in this file. ` This class makes use of the NestJS logger to print a warning message to the Terminal whenever a HTTP exception occurs. It also returns two custom fields within the response body whenever a HTTP exception occurs. The timestamp field reports when the exception occurred, and the endpoint field reports which route triggered the exception. In order to apply this filter, we need to decorate the PostsController with @UseFilters(new HttpExceptionFilter()). ` After saving these changes, NestJS will reload our application for us. If we use Insomnia to send a PUT /posts/1 request to our API, it should trigger a 404 NOT FOUND HTTP error, since no blog posts exist for updating in our application when it starts. The HTTP exception response body that is returned to Insomnia should now contain the timestamp and endpoint fields. ` We should also see the following line printed out to the Terminal. ` Summary In this article, we saw how NestJS makes back-end API development fast, simple, and effective. The NestJS application structure has helped us build a very well-organized project. We covered a lot, so let's recap what we learned: - How to use the NestJS CLI. - How to build feature modules in NestJS. - How to use services and controllers. - How to test an API with Insomnia. - How to add logging to NestJS apps. - How to use Swagger for documenting and previewing NestJS APIs. - How to get full control of HTTP exceptions in NestJS. Happy developing with NestJS! Code Visit the following GitHub repository in order to access the code featured in this article....

How to host a full-stack app with AWS CloudFront and Elastic Beanstalk cover image

How to host a full-stack app with AWS CloudFront and Elastic Beanstalk

How to host a full-stack JavaScript app with AWS CloudFront and Elastic Beanstalk Let's imagine that you have finished building your app. You have a Single Page Application (SPA) with a NestJS back-end. You are ready to launch, but what if your app is a hit, and you need to be prepared to serve thousands of users? You might need to scale your API horizontally, which means that to serve traffic, you need to have more instances running behind a load balancer. Serving your front-end using a CDN will also be helpful. In this article, I am going to give you steps on how to set up a scalable distribution in AWS, using S3, CloudFront and Elastic Beanstalk. The NestJS API and the simple front-end are both inside an NX monorepo The sample application For the sake of this tutorial, we have put together a very simple HTML page that tries to reach an API endpoint and a very basic API written in NestJS. The UI The UI code is very simple. There is a "HELLO" button on the UI which when clicked, tries to reach out to the /api/hello endpoint. If there is a response with status code 2xx, it puts an h1 tag with the response contents into the div with the id result. If it errors out, it puts an error message into the same div. ` The API We bootstrap the NestJS app to have the api prefix before every endpoint call. ` We bootstrap it with the AppModule which only has the AppController in it. ` And the AppController sets up two very basic endpoints. We set up a health check on the /api route and our hello endpoint on the /api/hello route. ` Hosting the front-end with S3 and CloudFront To serve the front-end through a CMS, we should first create an S3 bucket. Go to S3 in your AWS account and create a new bucket. Name your new bucket to something meaningful. For example, if this is going to be your production deployment I recommend having -prod in the name so you will be able to see at a glance, that this bucket contains your production front-end and nothing should get deleted accidentally. We go with the defaults for this bucket setting it to the us-east-1 region. Let's set up the bucket to block all public access, because we are going to allow get requests through CloudFront to these files. We don't need bucket versioning enabled, because these files will be deleted every time a new front-end version will be uploaded to this bucket. If we were to enable bucket versioning, old front-end files would be marked as deleted and kept, increasing the storage costs in the long run. Let's use server-side encryption with Amazon S3-managed keys and create the bucket. When the bucket is created, upload the front-end files to the bucket and let's go to the CloudFront service and create a distribution. As the origin domain, choose your S3 bucket. Feel free to change the name for the origin. For Origin access, choose the Origin access control settings (recommended). Create a new Control setting with the defaults. I recommend adding a description to describe this control setting. At the Web Application Firewall (WAF) settings we would recommend enabling security protections, although it has cost implications. For this tutorial, we chose not to enable WAF for this CloudFront distribution. In the Settings section, please choose the Price class that best fits you. If you have a domain and an SSL certificate you can set those up for this distribution, but you can do that later as well. As the Default root object, please provide index.html and create the distribution. When you have created the distribution, you should see a warning at the top of the page. Copy the policy and go to your S3 bucket's Permissions tab. Edit the Bucket policy and paste the policy you just copied, then save it. If you have set up a domain with your CloudFront distribution, you can open that domain and you should be able to see our front-end deployed. If you didn't set up a domain the Details section of your CloudFront distribution contains your distribution domain name. If you click on the "Hello" button on your deployed front-end, it should not be able to reach the /api/hello endpoint and should display an error message on the page. Hosting the API in Elastic Beanstalk Elastic beanstalk prerequisites For our NestJS API to run in Elastic Beanstalk, we need some additional setup. Inside the apps/api/src folder, let's create a Procfile with the contents: web: node main.js. Then open the apps/api/project.json and under the build configuration, extend the production build setup with the following (I only ) ` The above settings will make sure that when we build the API with a production configuration, it will generate a package.json and a package-lock.json near the output file main.js. To have a production-ready API, we set up a script in the package.json file of the repository. Running this will create a dist/apps/api and a dist/apps/frontend folder with the necessary files. ` After running the script, zip the production-ready api folder so we can upload it to Elastic Beanstalk later. ` Creating the Elastic Beanstalk Environment Let's open the Elastic Beanstalk service in the AWS console. And create an application. An application is a logical grouping of several environments. We usually put our development, staging and production environments under the same application name. The first time you are going to create an application you will need to create an environment as well. We are creating a Web server environment. Provide your application's name in the Application information section. You could also provide some unique tags for your convenience. In the Environment information section please provide information on your environment. Leave the Domain field blank for an autogenerated value. When setting up the platform, we are going to use the Managed Node.js platform with version 18 and with the latest platform version. Let's upload our application code, and name the version to indicate that it was built locally. This version label will be displayed on the running environment and when we set up automatic deployments we can validate if the build was successful. As a Preset, let's choose Single instance (free tier eligible) On the next screen configure your service access. For this tutorial, we only create a new service-role. You must select the aws-elasticbeanstalk-ec2-role for the EC2 instance profile. If can't select this role, you should create it in AWS IAM with the AWSElasticBeanstalkWebTier, AWSElasticBeanstalkMulticontainerDocker and the AWSElasticBeanstalkRoleWorkerTier managed permissions. The next step is to set up the VPC. For this tutorial, I chose the default VPC that is already present with my AWS account, but you can create your own VPC and customise it. In the Instance settings section, we want our API to have a public IP address, so it can be reached from the internet, and we can route to it from CloudFront. Select all the instance subnets and availability zones you want to have for your APIs. For now, we are not going to set up a database. We can set it up later in AWS RDS but in this tutorial, we would like to focus on setting up the distribution. Let's move forward Let's configure the instance traffic and scaling. This is where we are going to set up the load balancer. In this tutorial, we are keeping to the defaults, therefore, we add the EC2 instances to the default security group. In the Capacity section we set the Environment type to Load balanced. This will bring up a load balancer for this environment. Let's set it up so that if the traffic is large, AWS can spin up two other instances for us. Please select your preferred tier in the instance types section, We only set this to t3.micro For this tutorial, but you might need to use larger tiers. Configure the Scaling triggers to your needs, we are going to leave them as defaults. Set the load balancer's visibility to the public and use the same subnets that you have used before. At the Load Balancer Type section, choose Application load balancer and select Dedicated for exactly this environment. Let's set up the listeners, to support HTTPS. Add a new listener for the 443 port and connect your SSL certificate that you have set up in CloudFront as well. For the SSL policy choose something that is over TLS 1.2 and connect this port to the default process. Now let's update the default process and set up the health check endpoint. We set up our API to have the health check endpoint at the /api route. Let's modify the default process accordingly and set its port to 8080. For this tutorial, we decided not to enable log file access, but if you need it, please set it up with a separate S3 bucket. At the last step of configuring your Elastic Beanstalk environment, please set up Monitoring, CloudWatch logs and Managed platform updates to your needs. For the sake of this tutorial, we have turned most of these options off. Set up e-mail notifications to your dedicated alert e-mail and select how you would like to do your application deployments. At the end, let's configure the Environment properties. We have set the default process to occupy port 8080, therefore, we need to set up the PORT environment variable to 8080. Review your configuration and then create your environment. It might take a few minutes to set everything up. After the environment's health transitions to OK you can go to AWS EC2 / Load balancers in your web console. If you select the freshly created load balancer, you can copy the DNS name and test if it works by appending /api/hello at the end of it. Connect CloudFront to the API endpoint Let's go back to our CloudFront distribution and select the Origins tab, then create a new origin. Copy your load balancer's URL into the Origin domain field and select HTTPS only protocol if you have set up your SSL certificate previously. If you don't have an SSL certificate set up, you might use HTTP only, but please know that it is not secure and it is especially not recommended in production. We also renamed this origin to API. Leave everything else as default and create a new origin. Under the Behaviors tab, create a new behavior. Set up the path pattern as /api/* and select your newly created API origin. At the Viewer protocol policy select Redirect HTTP to HTTPS and allow all HTTP methods (GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE). For this tutorial, we have left everything else as default, but please select the Cache Policy and Origin request policy that suits you the best. Now if you visit your deployment, when you click on the HELLO button, it should no longer attach an error message to the DOM. --- Now we have a distribution that serves the front-end static files through CloudFront, leveraging caching and CDN, and we have our API behind a load balancer that can scale. But how do we deploy our front-end and back-end automatically when a release is merged to our main branch? For that we are going to leverage AWS CodeBuild and CodePipeline, but in the next blog post. Stay tuned....

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers cover image

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers

Before she was a software developer at freeCodeCamp, Jessica Wilkins was a classically trained clarinetist performing across the country. Her days were filled with rehearsals, concerts, and teaching, and she hadn’t considered a tech career until the world changed in 2020. > “When the pandemic hit, most of my gigs were canceled,” she says. “I suddenly had time on my hands and an idea for a site I wanted to build.” That site, a tribute to Black musicians in classical and jazz music, turned into much more than a personal project. It opened the door to a whole new career where her creative instincts and curiosity could thrive just as much as they had in music. Now at freeCodeCamp, Jessica maintains and develops the very JavaScript curriculum that has helped her and millions of developers around the world. We spoke with Jessica about her advice for JavaScript learners, why musicians make great developers, and how inclusive communities are helping more women thrive in tech. Jessica’s Top 3 JavaScript Skill Picks for 2025 If you ask Jessica what it takes to succeed as a JavaScript developer in 2025, she won’t point you straight to the newest library or trend. Instead, she lists three skills that sound simple, but take real time to build: > “Learning how to ask questions and research when you get stuck. Learning how to read error messages. And having a strong foundation in the fundamentals” She says those skills don’t come from shortcuts or shiny tools. They come from building. > “Start with small projects and keep building,” she says. “Books like You Don’t Know JS help you understand the theory, but experience comes from writing and shipping code. You learn a lot by doing.” And don’t forget the people around you. > “Meetups and conferences are amazing,” she adds. “You’ll pick up things faster, get feedback, and make friends who are learning alongside you.” Why So Many Musicians End Up in Tech A musical past like Jessica’s isn’t unheard of in the JavaScript industry. In fact, she’s noticed a surprising number of musicians making the leap into software. > “I think it’s because music and code have a lot in common,” she says. “They both require creativity, pattern recognition, problem-solving… and you can really get into flow when you’re deep in either one.” That crossover between artistry and logic feels like home to people who’ve lived in both worlds. What the Tech Community Is Getting Right Jessica has seen both the challenges and the wins when it comes to supporting women in tech. > “There’s still a lot of toxicity in some corners,” she says. “But the communities that are doing it right—like Women Who Code, Women in Tech, and Virtual Coffee—create safe, supportive spaces to grow and share experiences.” She believes those spaces aren’t just helpful, but they’re essential. > “Having a network makes a huge difference, especially early in your career.” What’s Next for Jessica Wilkins? With a catalog of published articles, open-source projects under her belt, and a growing audience of devs following her journey, Jessica is just getting started. She’s still writing. Still mentoring. Still building. And still proving that creativity doesn’t stop at the orchestra pit—it just finds a new stage. Follow Jessica Wilkins on X and Linkedin to keep up with her work in tech, her musical roots, and whatever she’s building next. Sticker illustration by Jacob Ashley....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co