6

Developing Web APIs with Node

 1 year ago
source link: https://devm.io/nodejs/developing-web-apis-with-node
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Intro to Node.js part 2

Developing Web APIs with Node

22. Aug 2022


One of the most common uses of Node.js is the development of web APIs. Numerous modules from the community are available for this, covering a whole range of aspects, such as routing, validation, and CORS.

The first part of this series introduced Node.js as a server-side runtime environment for JavaScript and showed how to write a simple web server. In addition, the package management npm was introduced, which allows us to easily install modules written by the community into our own application. So, we already know some of the basics, but the developed application still lacks meaningful functionality.

This will change in this part of the series: The application, which so far only launches a rudimentary web server, is supposed to provide an API that can be used to manage a task list. First, it is necessary to make some technical preliminary considerations, because we must define what exactly the application is supposed to do. For example, the following functions are possible:

  • It must be possible to write down a new task. In the simplest form, this task consists of only a title, which must not be empty.
  • It must also be possible to call up a list of all tasks that still need to be done, in order to see what still needs doing.
  • Last but not least, it must be possible to check off a completed task so that it is removed from the todo list.

These three functions are essential, without them a task list cannot be used meaningfully. All other functions, such as renaming a task or undoing the check-off of a task, are optional. Of course, it would make sense to implement them in order to make the application as user-friendly and convenient as possible – but they are not really necessary. The three functions mentioned above represent the scope of a Minimum Viable Product (MVP), so to speak.

Another restriction should be specified right at the beginning: The task list shall deliberately not have user management in order to keep the example manageable. This means that there will be neither authentication nor authorization, and it will not be possible to manage multiple task lists for different people. This would be essential to use the application in production, but it is beyond the scope of this article and ultimately offers little learning for Node.js.

Current state

The current state of the application we wrote in the first part includes two code files: app. js, which starts the actual server, and lib/getApp.js, which contains the functionality to respond to requests from the outside. In the app.js file, we already used the npm module processenv [1] to be able to set the port to a value other than the default 3000 via an environment variable (Listing 1).

Listing 1

'use strict';


const getApp = require('./lib/getApp');
const http = require('http');
const { processenv } = require('processenv');


const port = processenv('PORT', 3000);


const server = http.createServer(getApp());


server.listen(port);

The good news is that at this point, nothing will change in this file. This is because there is already a separation of content in the app.js and getApp.js files: The first file takes care of the HTTP server itself, while the second contains the actual logic of the application. In this part of the article series, only the application logic will be adapted and extended, so the app.js file can remain as it is.

However, the situation is different in the getApp.js file, where we will leave no stone unturned. But, one thing at a time. First, the package.json file must be modified so that the name of the application is more meaningful. For example, instead of my-http-server, the application could be called tasklist:

{
  "name": "tasklist",
  "version": "0.0.1",
  "dependencies": {
    "processenv": "3.0.2"
  }
}

The file and directory structure of the application still looks the same as in the first part:

/
  lib/
    getApp.js
  node_modules/
  app.js
  package.json
  package-lock.json

REST? No thanks!

Now it’s a matter of incorporating routing. As usual with APIs, this is done via different paths in the URLs. In addition, you can fall back on the different HTTP verbs such as GET and POST to map different actions. A common pattern is the so-called REST approach, which specifies that so-called resources are defined via the URL and the HTTP verbs define the actions on these resources. The usual mapping according to REST is as follows:

  • POST creates a new resource, and corresponds to a Create.
  • GET retrieves a resource, and represents the classic Read.
  • PUT updates a resource, and corresponds to an Update.
  • DELETE finally deletes a resource, and corresponds to a Delete.

As you can see, these four HTTP verbs can be easily mapped to four actions of the so-called CRUD pattern, which in turn corresponds to the common approach of how to access data in (relational) databases. This is one of the most important reasons for the success of REST: It is simple and builds on the already familiar logic of databases. Nevertheless, there are some reasons against using this transfer of CRUD to the API level. The most weighty of these is that the verbs do not conform to the technical language: Users do not talk about creating or updating a task.

Instead, they think in terms of technical processes: They want to make a note of a task or check off a task as completed. This is where a business and a technical view collide. It is obvious that a mapping between these views must take place at some point – but the code of an application should tend to be structured in a domain-oriented rather than a technical way [2]. After all, the application is written to solve a domain-oriented problem, and technology is merely the means to an end. Seen in this light, CRUD is also an antipattern [3].

An alternative approach is provided by the CQRS pattern, which is based on commands and queries [4]. A command is an action that changes the state of the application and reflects a user’s intention. A command is usually in the imperative, since it is a request to the application to do something. In the context of the task list, there are two actions that change the state of the list, noting and checking off a task. If we formulate these actions in the imperative and translate them into English, we get phrases such as “Note a todo.”, “Tick off a todo.”

Analogously, you can formulate a query, i.e. a query that doesn’t change the state of the application, but returns it. This is the difference between a command and a query: A command writes to the application, so to speak, while a query reads from the application. The CQRS pattern states that every interaction with an application should be either a command or a query – but never both at the same time. In particular, this means that Commands should not return the current state of the task list, but that a separate Query is needed for that: For example: “Get pending todos.”

If we abandon the idea that an API must always be structured according to REST and prefer the much simpler pattern of separating writing and reading, the question arises as to how the URLs should be structured and which HTTP verbs should be used. In fact, the answer to this question is surprisingly simple: The URLs are formulated exactly as mentioned above, POST for commands, and GET for queries are used as HTTP verbs – that’s it. This results in the following routes:

  • POST /note-todo
  • POST /tick-off-todo
  • GET /pending-todos

The beauty of this approach is that it is much more self-explanatory than REST. POST /tick-off-todo is much more technical than a PUT /todo. Here, it is clear that an update is executed, but which functional purpose this update has is unclear. When there are different reasons for initiating a (technical) update, the semantically stronger approach gains a lot in comprehensibility and traceability.

Define routes

Now it is necessary to define the appropriate routes. However, this is not done with Node.js’s on-board tools. Instead, we can use the npm module Express [5]:

$ npm install express
$ npm install express

The module can now be loaded and used within the getApp.js file. First, an express application has to be defined, for which only the express function has to be called. Then, the get and post functions can be used to define routes, specifying the desired path name and a callback – similar to the one used in the standard Node.js server (Listing 2).

Listing 2

'use strict';


const express = require('express');


const getApp = function () {
  const app = express();


  app.post('/note-todo', (req, res) => {
    // ...
  });


  app.post('/tick-off-todo', (req, res) => {
    // ...
  });


  app.get('/pending-todos', (req, res) => {
    // ...
  });


  return app;
};


module.exports = getApp;

With this, the basic framework for the routes is already built. The individual routes can, of course, also be swapped out into independent files, but for the time being, focus should be on implementing functionality. The next step is to implement a task list, which is initially designed as a pure in-memory solution. However, since it will be backed by a database in a future part of this series, it will be designed from the outset to be seamlessly extensible later. Essentially, this means that all functions to access the task list will be created asynchronously, since accesses to databases in Node.js are usually...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK