How to build microservices with Docker - The Services
Tags: microservices, tutorial, node, python
Published: 2023-06-26 14:01:14

Hello everyone and welcome back,
Today we'll cover the second part of the microservices application - the code for the individual services.
To quickly recall from the [first part](https://dev.to/sainig/how-to-build-microservices-with-docker-introduction-4939), the services we'll build today are:
- **Products:** Used to list and search for products.
- **Orders:** Used to place new orders. This is protected by the auth server so users can't place orders unless they're logged in.
- **Notifications:** Used to notify users when a new order is placed.
- **Auth:** Used to check user authentication before placing orders.

So, without any more delay, let's start

> PS: The code here is incomplete, for the full code check out the [Github repo](https://github.com/saini-g/microservices-shop)

---

## Products Service

This will be a simple Node, Express REST API. There's one `GET` endpoint to list all products. The code is like:

```js
const express = require('express');
const app = express();

app.get('/', (req, res, next) => {
  const { ids } = req.query;
  let resultProducts = products;

  if (ids) {
    const productIds = ids.split(',');
    resultProducts = products.filter((p) => {
      return productIds.includes(p.id));
    }
  }
  res.status(200).json(resultProducts);
});
```

You'll notice we have a query parameter named `ids`. We'll use this to fetch a subset of all the products by their id.

Also, for simplicity, the complete list of products is coming from a local variable [here](https://github.com/saini-g/microservices-shop/blob/main/products/src/app.js#L41).

---

## Orders Service

This is also a Node, Express REST API, but not quite as simple as the products one. There's still only one `POST` endpoint, but since it does multiple things, let's look at the code in pieces.

First, we extract the `userId` and `productIds` from the request body and use the `productIds` to fetch more product details (mainly prices and names) from the products service:

```js
const { productIds, userId } = req.body;

const { data: orderedProducts } = await axios.get(
  `${PRODUCTS_APP_HOST}/?ids=${productIds.join(',')}`
);
```

Then, we calculate the total amount for the order and a comma separated names list of the products:

```js
let totalAmount = 0;
let orderedProductNames = '';

for (const product of orderedProducts) {
  totalAmount += product.price;
  orderedProductNames += orderedProductNames === '' ? product.name : `, ${product.name}`
}
```

Lastly, we publish a message for the `nats-broker` (we'll look at the notification service next) and send a response back to the API request. Similar to the products service, there's no database here also, so we just return a plain JSON object with a random id in the response.

```js
natsClient.sendMessage(
  NEW_ORDER_MESSAGING_CHANNEL,
  `Hello ${userId}, your order is confirmed. Products: ${orderedProductNames}`
);

res.status(201).json({
  productIds,
  totalAmount,
  userId,
  id: Math.floor(Math.random() * 9999)
});
```

---

## Notifications Service

This service is a Nats subscriber, listening for messages published to the "new order placed channel". For now, we just log the incoming messages to the console, but in a real world scenario we can notify users via SMS, Emails, etc.
The code consists of 2 functions, one is the handler for messages and another to start the subscriber.

```js
function handleMessages(err, message) {
  if (err) {
    console.log(`Error while reading messages: ${err}`);
    return;
  }
  console.log(`Message received on channel "${NEW_ORDER_MESSAGING_CHANNEL}"`);
  console.log(message);
}

async function main() {
  await natsClient.startup();
  natsClient.subscribe(NEW_ORDER_MESSAGING_CHANNEL, handleMessages);
}

main().catch(console.log);
```

### The `nats-client` code

This is a common file and can be used by any service who wants to talk to the Nats broker.

```js
const { connect, StringCodec } = require('nats');

const NATS_BROKER_URL = process.env.NATS_BROKER_URL;

class NatsClient {
  #conn;
  #codec;

  constructor() {
    this.#codec = StringCodec();
  }

  async startup() {
    this.#conn = await connect({ servers: NATS_BROKER_URL });
    console.log(`Connected to nats broker on "${this.#conn.getServer()}"`);
  }

  sendMessage(channel, message) {
    this.#conn.publish(
      channel,
      this.#codec.encode(message)
    );
  }

  subscribe(channel, cb) {
    this.#conn.subscribe(channel, {
      callback: (err, payload) => {
        cb(err, this.#codec.decode(payload.data))
      }
    });
    console.log(`Subscribed to messaging channel "${channel}"`);
  }
}
```

Currently, the orders service uses the `sendMessage` method and the notifications service uses the `subscribe` method.

---

## Auth Service

This is again a REST API. But, just to mix things up a bit, it's built in Python using the [FastAPI](https://fastapi.tiangolo.com/) framework. Currently, there's only one endpoint to verify the `Authorization` header, which checks for a hardcoded token value.

```python
@app.get("/verify")
def verify_token(authorization: Annotated[str | None, Header()] = None):
    if authorization != "secret-auth-token":
        return JSONResponse(
            status_code=status.HTTP_401_UNAUTHORIZED,
            content=None
        )

    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content=None
    )
```

---

## Wrap up

That was all for the individual services code. In the next part we'll look at the root level `docker-compose.yml` files and the `nginx.conf` file to start all the components of the application and make things work.

I hope you'll enjoy this series and learn something new.

Feel free to post any questions you have in the comments below.

Cheers!