Intro to Nest.js: The higher
By Matthew Tyson
Software Architect, InfoWorld |
Not to be confused with Next.js, Nest.js is a newer and unique approach to JavaScript server technology. It takes a familiar server like Express or Fastify and layers on a number of useful abstractions, which are geared toward empowering and simplifying higher-level application design. Thanks to its distinctive blend of programming paradigms, first-order TypeScript support, and built-in features like dependency injection, Nest.js has steadily grown in popularity over the last few years.
Nest.js is an interesting contribution to the JavaScript ecosystem, and well worth your attention. It's a great tool to keep in mind when working with server-side JavaScript and TypeScript.
In this article, we’ll do a whirlwind tour of Nest.js, with examples including routing, controllers, producers (dependency injection), and authentication with guards. You'll also get an understanding of the Nest.js module system.
Our example is an application used for managing a list of pasta recipes. We’ll include a dependency-injected service that manages the actual dataset and a RESTful API we can use to list all recipes or recover a single recipe by ID. We'll also set up a simple authenticated PUT endpoint for adding new recipes.
Let’s start by scaffolding a new project. Once have that, we can dive into the examples.
We can use the Nest.js command-line interface to set up a quick application layout, starting with installing Nest globally with: $ npm install -g @nestjs/cli. In addition to the create command, nestjs includes useful features like generate for sharing reusable designs. Installing globally gives us access to that and more.
Now we can create a new application with: $ nest new iw-nest. You can select whichever package manager you want (npm, yarn, or pnpm). For this demo, I’ll use pnpm. The process is the same regardless.
Change into the new /iw-nest directory and start the dev server with: $ pnpm run start. You can verify the application is running by visiting localhost:3000, where you should see a “Hello, World!” message. This message is coming from the iw-nest/src/app.controller.ts. If you look at that file, you can see it's using an injected service. Let’s create a new controller (src/recipes.controller.ts) that returns a list of recipes, as shown in Listing 1.
Listing 1 gives us a look at the basics of routing in Nest.js. You can see we use the @Controller(‘recipes’) annotation to define the class as a controller with the route of /recipes. The getRecipes() method is annotated to handle the GET method with @Get().
For now, this controller simply maps the /recipes GET to a hard-coded response string. Before Nest.js will serve this, we need to register the new controller with the module. Modules are another important concept in Nest, used to help organize your application code. In our case, we need to open /src/app.module.ts and add in the controller, as shown in Listing 2.
The dependency injection framework in Nest.js is reminiscent of Spring in the Java ecosystem. Having built-in dependency injection alone makes Nest.js worth considering, even without its other bells and whistles.
We’re going to define a service provider and wire it to our controller. This is a clean way to keep the application organized into layers. You can see our new service class, /src/recipes.service.ts, in Listing 3.
To use this service provider, we also need to add it to the app.module.ts file, as shown in Listing 4.
Modules are a good way to organize an application. They can act as a logical grouping mechanism, providing a hierarchical structure where the most fundamental modules are clearly defined and the others depend on them.
Now we can use the service in the RecipesController, as shown in Listing 5. If you are new to dependency injection, this might seem like a lot of extra work. But the ability to define and consume classes application-wide, in a standardized way, can be a real boon to your application architecture as the system grows.
Essentially, we import the RecipesService class, then to get a reference to it, we use the @Inject() annotation on the recipesService member. The injection system will wire this to an instance of the RecipesService class, based on the type. By default, injected services are singletons in Nest, so all client classes will get the reference to the same instance. It’s possible to use other “scopes” for services to fine-tune how they are instantiated. Besides constructor injection, Nest supports property-based injection.
Now, if you run the application and go to localhost:3000/recipes, you’ll see the JSON output from the array of recipes in the service.
Now, let’s add a new POST endpoint to allow users to add recipes. We'll secure it with the simplest possible authentication using what's called a guard in Nest.js. A guard sits in front of the controllers and determines how the requests are routed. You can see our simple guard in Listing 6. Right now, it just checks whether there is an authentication header in the request.
Next, register the guard with the module, as shown in Listing 7.
Now we can use the new guard service to protect our POST endpoint, shown in Listing 8. Note the new imports.
Notice that the @UserGuards annotation was used to apply the new guard to the addRecipe() method, which is also specified as a POST endpoint with the @Post annotation. Nest will take care of instantiating and applying the guard to the endpoint for us.
We’ve also added an addRecipe() method to the service, which is very simple, as shown in Listing 9.
Now we can test out the authentication and endpoint with a couple of CURL requests, as in Listing 10.
You can see the authorization is working, as only a request holding the Bearer header is allowed through.
So far, we have used just a JavaScript object. In the TypeScript world, it would be common to create a Recipe model object and use it as a value object to shuttle around information. For example, we could create the Recipe class (Listing 11) and use it in the addRecipe method (Listing 12).
Finally, you can make the addRecipe() POST method strongly typed and Next will automatically populate the model object for us:
You can then make the addRecipe() POST method strongly typed and Nest will automatically populate the model object for us, as shown in Listing 13.
The choice between the flexibility of JavaScript's duck typing and TypeScript's strong typing is really down to what you, the team, or the organization decide to use. JavaScript gives you speed of development and flexibility, whereas TypeScript gives you structure and more tooling support.
It’s also worth noting that Nest.js embraces reactive programming, and you can return promises from methods and endpoints. Even more, you can return an RxJS Observable. This gives you powerful options for wiring applications together with asynchronous data streams.
Although we've only scratched the surface of what it can do, it’s clear that Nest.js is a well-thought-out and capable platform for building Node servers. It delivers on the promise of a higher-level layer on top of Express for improved architecture and design support. If you are looking to build server-side JavaScript and especially TypeScript applications, Nest is a great option.
Next read this:
Matthew Tyson is a founder of Dark Horse Group, Inc. He believes in people-first technology. When not playing guitar, Matt explores the backcountry and the philosophical hinterlands. He has written for JavaWorld and InfoWorld since 2007.
Copyright © 2023 IDG Communications, Inc.
Next read this: