Defining Routes in Hapi
Tags: javascript, node, webdev, hapi
Published: 2021-02-19 14:05:03

Today Joe has plenty of time to learn more about Hapi. His target is learning how to add routes to his server so it can accept requests for creating and modifying data. And obviously, he also wants to be able to respond to the request back.

Before going straight to the documentation, Joe tries to guess it. He has a strong feeling that routing in Hapi has something to do with the server object. Because from the last learning session, he knew that the server object is the main application container. And if it has a bunch of properties such as `server.info`, then it should also have a bunch of methods and one of them must be a route-related method. That what's Joe thinking about.

Then he checks his hypothesis by visiting the documentation, especially the server object API section. And yes he is right. Indeed Hapi has `server.route()` which responsible for defining route endpoints. Finding his hypothesis get validated, he intends to use that educated guess more often in the future.

## Creating Endpoints

The `server.route()` accepts an object as the only parameter with a format of `{METHOD, PATH, HANDLER}`, which is self-explanatory.

- method is a string that refers to the name of the HTTP method Joe wants to respond. It accepts any valid HTTP method.
- path is a string that refers to the actual path that the user will type in the browser (following the hostname).
- handler is a function where he puts his code for that particular route

Given that new knowledge, Joe creates a test route that accepts `GET` request. Inside the handler, he returns a string that said 'OK'.

```js
server.route({
  method: "GET",
  path: "/test",
  handler() {
    return "OK";
  },
});
```

He starts his server and the terminal greets him with the server info which means his code from the previous coding session is still work. Then he opens his favorite web browser and types out the `http://localhost:3000` followed by the route path `/test` at the address bar. When he hits enter, the browser shows 'OK'. Great!

Now Joe is ready to replicate the successful test to his initial mission. What is his mission anyway? The point of sale app! After reviewing his [mission brief](https://dev.to/jprumekso/learn-hapi-mysql-introduction-5276#the-mission), Joe decides that he'll start with the store resource.

He wants the `GET` stores route to return an array of store objects. However, since he doesn't want to work with the database yet, he will just hardcoded it.

So here is Joe's simple pseudo database:

```js
const stores = [
  {
    id: 1,
    name: "JoeTech Store 1",
    address: "East Java, Indonesia",
  },
  {
    id: 2,
    name: "JoeTech Store 2",
    address: "Lombok, Indonesia",
  },
  {
    id: 3,
    name: "JoeTech Store 3",
    address: "Bali, Indonesia",
  },
];
```

And for the `GET` store route his code looks like this:

```js
server.route({
  method: "GET",
  path: "/api/stores",
  handler() {
    return stores;
  },
});
```

As we can see, he prefixes the stores path with `api` and uses a plural form for the resource name. Where does he get that information? His best friend, Google. And for the handler, he writes it in the shorthand syntax which is introduced in ES6.

One more question, where does he put that code? At first, he puts it inside the init function following the example at Hapi documentation. But when he found that his code still works even if it placed outside the init function, he chooses to place it there. He thinks that it's cleaner that way.

So now his `app.js` code looks like this:

```js
const Hapi = require("@hapi/hapi");

const server = Hapi.server({
  port: 3000,
  host: "localhost",
});

const stores = [
  {
    id: 1,
    name: "JoeTech Store 1",
    address: "East Java, Indonesia",
  },
  {
    id: 2,
    name: "JoeTech Store 2",
    address: "Lombok, Indonesia",
  },
];

server.route({
  method: "GET",
  path: "/test",
  handler() {
    console.log("it works...");
    return "OK";
  },
});

server.route({
  method: "GET",
  path: "/api/stores",
  handler() {
    return stores;
  },
});

const init = async () => {
  try {
    await server.start();
    console.log("Server started...");
    console.log(server.info);
  } catch (error) {
    console.log(error);
  }
};

init();
```

Then he opens his browser again and going to `http://localhost:3000/api/stores`. When he hits enter, the browser gives him this beautiful response, which is the exact same stores array he created before:

```bash
[
  {
    id: 1,
    name: "JoeTech Store 1",
    address: "East Java, Indonesia",
  },
  {
    id: 2,
    name: "JoeTech Store 2",
    address: "Lombok, Indonesia",
  }
]
```

## Testing API using REST Client Plugin

Joe realizes that testing his api using a browser will only work for `GET` endpoint. How about the other endpoints? From a lot of tutorials on Youtube, he knew that the most common way to test API is using Postman. But he wonders if there is a more simple approach to accomplish this task. Then he remembers something...

From his frontend development experience, he found that his favorite code editor, the VS Code, has many plugins available (thanks to the wonderful community). So he thought maybe there is a plugin for this particular task. So he goes to his best friend asking this matter. Here is what he asks Google: "how to make api call from vscode". Without much thinking, his friend gives him a lot of information. But there is one particular piece of info that Joe thinks would work for him, the REST Client plugin by Huachao Mao.

How to use REST Client plugin to test api?

The documentation says that first, he needs to create a file with `.http` extension. Joe names it `ApiTest.http`. Then he needs to write the request with a format like this `METHOD URI HTTP/1.1`. So here is what he writes for testing the test route:

```http
GET http://localhost:3000/test HTTP/1.1
```

And to send a payload we write the request like this:

```http
POST http://localhost:3000/test HTTP/1.1
content-type: application/json

{
    "message": "Hello"
}
```

Then to execute the request he needs to click the `Send Request` link at the top of the file.

Joe thinks that this plugin is surprisingly easy and intuitive. Thanks to this plugin, now Joe doesn't need to go back and forth between his code editor and Postman.

## Query Parameter

Joe wants the user of his app to be able to search stores by name. He thinks this feature is important when his user has many stores. Also, it's pretty common by the way.

In this case, Joe needs to know how to get the value of the query parameter so when he types `.../stores?name=something` he can catch that 'something' and use it to filters the stores data.

The documentation says that Joe can access the query parameter's value from the request parameter object. It's available as the first parameter of the route handler function. It can be named anything, however, the common one is `request` or `req` for short.

Then Joe tries to implement it. Here is his code:

```js
server.route({
  method: "GET",
  path: "/api/stores",
  handler(req) {
    const { name } = req.query;

    if (name) {
      return stores.filter((store) => store.name === name);
    }

    return stores;
  },
});
```

Now he wants to test this new capability as well as testing the REST Client plugin for "a real use case". Here is what he writes at `ApiTest.http`:

```http
GET http://localhost:3000/api/stores?name=JoeTech Store 1 HTTP/1.1
```

And here is the result after he clicks the Send Request:

```http
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-cache
content-length: 68
accept-ranges: bytes
connection: close
Date: Wed, 17 Feb 2021 21:48:37 GMT

[
  {
    "id": 1,
    "name": "JoeTech Store 1",
    "address": "East Java, Indonesia"
  }
]

```

This way, his `GET` stores route is capable of filtering the stores based on the name specified by the user via the query parameter. Awesome!

## Defining Path Parameter

Listing all stores is only half useful if the user can't see each store's details. So now Joe wants to create a route for retrieving a single store by its id.

What does he need to know to achieve this goal? He needs to know how to define and access a path parameter in Hapi.

The documentation says that to define a path parameter Joe needs to wrap the name of the parameter with curly braces and simply include it in the path. In this case, what Joe wants to do is writing the route's path in this fashion: `/api/stores/{id}`. Then he can access that 'id' from the same request object above, specifically from the `params` property.

After understanding that explanation, Joe writes the `GET` single store route. His code looks like this:

```js
server.route({
  method: "GET",
  path: "/api/stores/{id}",
  handler(req) {
    const { id } = req.params;
    return stores.find((store) => store.id === id);
  },
});
```

His code looks good. He uses the `req.params` to access the id. Or more precisely, he uses ES6 destructuring to extract the id from `req.params`.

However, when he runs it, he gets this instead of the data of store with id 1:

```http
HTTP/1.1 500 Internal Server Error
content-type: application/json; charset=utf-8
cache-control: no-cache
content-length: 46
Date: Wed, 17 Feb 2021 06:56:29 GMT
Connection: close

{
  "statusCode": 500,
  "error": "Internal Server Error",
  "message": "An internal server error occurred"
}
```

And when he checks the terminal, it says:

```bash
Debug: internal, implementation, error
    Error: handler method did not return a value, a promise, or throw an error
    ...
```

When he checks whether the id is obtained successfully using console.log(), it is. But why it still gives him an error? What's wrong?

Then he realizes that the path parameter is a string and he uses a strict equality operator to compare it with the store's id which is an integer, of course, he gets an error. So he fixes his code by parsing the path parameter's id to an integer. His code looks like this now.

```js
server.route({
  method: "GET",
  path: "/api/stores/{id}",
  handler(req) {
    const { id } = req.params;
    return stores.find((store) => store.id === parseInt(id));
  },
});
```

Now `GET` request to http://localhost:3000/api/stores/1 returns:

```http
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-cache
content-length: 46
Date: Wed, 17 Feb 2021 16:56:29 GMT
Connection: close

{
  "id": 1,
  "name": "JoeTech Store 1",
  "address": "East Java, Indonesia"
}
```

It works!

## Capturing Payload

Now Joe wants to work on the create store route. For this task, Joe needs to know how to capture the payload. So how does Hapi handling this matter?

Like the path parameter, the payload is also accessible via request object. For example, if Joe wants to echo out the user's payload at his POST `/api/stores`, then the code will look like this:

```js
server.route({
  method: "POST",
  path: "/api/stores",
  handler(req) {
    return req.payload;
  },
});
```

After understanding the method of getting the user's payload then Joe implements the "real" logic for the create new store route.

```js
server.route({
  method: "POST",
  path: "/api/stores",
  handler(req) {
    const newStore = {
      id: stores.length + 1,
      name: req.payload.name,
      address: req.payload.address,
    };

    stores.push(newStore);

    return newStore;
  },
});
```

Since he uses an array as dummy data, he only needs to catch the incoming store data and push it into the stores array. The data from the payload is passed unchanged except for the id which he manually adds by incrementing the array length by one. Of course, when he uses a database later the code will be more complex than this. After the store is added, he is returning the newly created store.

Now it's time to test it. Joe opens the `ApiTest.http` file and writes the following to create a post request to his new endpoint:

```http
POST http://localhost:3000/api/stores HTTP/1.1
content-type: application/json

{
  "name": "Amalina",
  "address": "Jakarta, Indonesia"
}
```

When he clicks Send Request, he gets the following response:

```http
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-cache
content-length: 46
Date: Thu, 18 Feb 2021 14:06:29 GMT
Connection: close

[
  {
    "id": 3,
    "name": "Amalina",
    "address": "Indonesia"
  }
]
```

It means his code is working! Good job Joe!

There is only one route left, the edit store route which accepts a `PUT` request. Joe thinks that all of his routes, this is the most complex one. This route requires him to catch the path parameter as well as the payload. Thankfully, since he already nails the concept into his head, this complex task becomes easy for him. So here is his code:

```js
server.route({
  method: "PUT",
  path: "/api/stores/{id}",
  handler(req) {
    const { id } = req.params;

    const theStore = stores.find((store) => store.id === parseInt(id));

    theStore.name = req.payload.name;
    theStore.address = req.payload.address;

    return theStore;
  },
});
```

At the handler function, Joe grab the store's id from the path parameter. Then he uses that id to find the store. Then he updates the store data with the incoming payload.

He told by the internet that the `PUT` request should send a full resource. So even if he only wants to update certain property, he still need to send the full resource. So he needs to provide all the neccessary means to edit all store's properties: `theStore.id`, `theStore.name` and `theStore.address`. Surely he needs to find a more elegant way in the future when the store's details is not just name and address anymore.

Also he found many warnings to think that `POST` is exclusively for creating resource and `PUT` is exclusively for editing resource. Many people said that that's wrong! In fact, both `POST` and `PUT` can be used to create and edit a resource. Even though, in practice, he found many tutorials that simply map `POST` for create operation and `PUT` for edit operation.

Being a good beginner, who doesn't have much experiences yet, he tries to follow what the community said as well as what is make sense for him.

Then Joe tries his new route by sending a `PUT` request to modify the name of the store with id 1.

```http
PUT http://localhost:3000/api/stores/1 HTTP/1.1
content-type: application/json

{
  "id": 1,
  "name": "J-Tech",
  "address": Indonesia
}
```

And here is what he gets:

```http
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-cache
content-length: 46
Date: Fri, 19 Feb 2021 06:56:29 GMT
Connection: close

{
  "id": 1,
  "name": "J-Tech",
  "address": "Indonesia"
}
```

Thank God, it works!

Having all store routes completed, Joe decides to call it a day. Joe feels happy as he has learned the skill to create routes in Hapi. For the next session, he wants to learn about validation in Hapi so he can make his app more stable.