Skip to content

nodejs design

devonfw-core edited this page Jan 27, 2022 · 10 revisions

NodeJS design (deprecated)

Introduction

The NodeJS back-end for My Thai Star application is going to be based on:

  • ExpressJS as the web application framework

  • devon4node as data access layer framework

  • DynamoDB as NoSQL Database

To know more details about the above technologies please visit the following documentation:

Basic architecture details

This structure can be shown in the following example image:

folder organization
  • public - All files which be exposed on the server directly

  • src

    • database folder - Folder with scripts to create/delete/seed the database

    • model - Folder with all data model

    • routes - Folder with all ExpressJS routers

    • utils - Folder with all utils like classes and functions

    • app.ts - File with ExpressJS declaration

    • config.ts - File with server configs

    • logic.ts - File with the business logic

  • test - Folder with all tests

Layers

  • Service Layer: this layer will expose the REST API to exchange information with the client applications.

  • Logic Layer: the layer in charge of hosting the business logic of the application.

  • Data Access Layer: the layer to communicate with the data base.

Service layer

The services layer will be solved using REST services with ExpressJS

To give service to the defined User Stories we will need to implement the following services:

  • provide all available dishes.

  • save a booking.

  • save an order.

  • provide a list of bookings (only for waiters) and allow filtering.

  • provide a list of orders (only for waiters) and allow filtering.

  • login service (see the Security section).

  • provide the current user data (see the Security section)

In order to be compatible with the other back-end implementations, we must follow the naming conventions proposed for Devon4j applications. We will define the following end points for the listed services.

  • (POST) /mythaistar/services/rest/dishmanagement/v1/dish/search.

  • (POST) /mythaistar/services/rest/bookingmanagement/v1/booking.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order.

  • (POST) /mythaistar/services/rest/bookingmanagement/v1/booking/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/filter (to filter with fields that does not belong to the Order entity).

  • (POST) /mythaistar/login.

  • (GET) /mythaistar/services/rest/security/v1/currentuser/.

You can find all the details for the services implementation in the Swagger definition included in the My Thai Star project on Github.

To treat these services separately, the following routers were created:

  • bookingmanagement: will answer all requests with the prefix /mythaistar/services/rest/bookingmanagement/v1

  • dishmanagement: will answer all requests with the prefix /mythaistar/services/rest/dishmanagement/v1

  • ordermanagement: will answer all requests with the prefix /mythaistar/services/rest/ordermanagement/v1

These routers will define the behavior for each service and use the logical layer.

An example of service definition:

router.post('/booking/search', (req: types.CustomRequest, res: Response) => {
    try {
        // body content must be SearchCriteria
        if (!types.isSearchCriteria(req.body)) {
            throw {code: 400, message: 'No booking token given' };
        }

        // use the searchBooking method defined at business logic
        business.searchBooking(req.body, (err: types.Error | null, bookingEntity: types.PaginatedList) => {
            if (err) {
                res.status(err.code || 500).json(err.message);
            } else {
                res.json(bookingEntity);
            }
        });
    } catch (err) {
        res.status(err.code || 500).json({ message: err.message });
    }
});

Logic layer and Data access layer

In the logic layer we will locate all the business logic of the application. It will be located in the file logic.ts. If in this layer we need to get access to the data, we make use of data access layer directly, in this case using devon4node with the DynamoDB adapter.

Example:

export async function cancelOrder(orderId: string, callback: (err: types.Error | null) => void) {
    let order: dbtypes.Order;

    try {
        // Data access
        order = await oasp4fn.table('Order', orderId).promise() as dbtypes.Order;

        [...]
    }
}

We could define the data access layer separately, but devon4node allows us to do this in a simple and clear way. So, we decided to not separate the access layer to the logic business.

Security with Json Web Token

For the Authentication and Authorization the app will implement the json web token protocol.

JWT basics

Refer to JWT basics for more information.

JWT implementation details

The Json Web Token pattern will be implemented based on the JSON web token library available on npm.

Authentication

Based on the JSON web token approach, we will implement a class Authentication to define the security entry point and filters. Also, as My Thai Star is a mainly public application, we will define here the resources that won’t be secured.

List of unsecured resources:

  • /services/rest/dishmanagement/\**: to allow anonymous users to see the dishes info in the menu section.

  • /services/rest/ordermanagement/v1/order: to allow anonymous users to save an order. They will need a booking token but they won’t be authenticated to do this task.

  • /services/rest/bookingmanagement/v1/booking: to allow anonymous users to create a booking. Only a booking token is necessary to accomplish this task.

  • /services/rest/bookingmanagement/v1/booking/cancel/\**: to allow canceling a booking from an email. Only the booking token is needed.

  • /services/rest/bookingmanagement/v1/invitedguest/accept/\**: to allow guests to accept an invite. Only a guest token is needed.

  • /services/rest/bookingmanagement/v1/invitedguest/decline/\**: to allow guests to reject an invite. Only a guest token is needed.

To configure the login we will create an instance of Authentication in the app file and then we will use the method auth for handle the requests to the /login endpoint.

app.post('/mythaistar/login', auth.auth);

To verify the presence of the Authorization token in the headers, we will register in the express the Authentication.registerAuthentication middleware. This middleware will check if the token is correct, if so, it will place the user in the request and continue to process it. If the token is not correct it will continue processing the request normally.

app.use(auth.registerAuthentication);

Finally, we have two default users created in the database:

  • user: waiter

  • password: waiter

  • role: WAITER

  • user: user0

  • password: password

  • role: CUSTOMER

Token set up

Following the official documentation the implementation details for the MyThaiStar’s JWT will be:

  • Secret: Used as part of the signature of the token, acting as a private key. It can be modified at config.ts file.

  • Token Prefix schema: Bearer. The token will look like Bearer <token>

  • Header: Authorization. The response header where the token will be included. Also, in the requests, when checking the token it will be expected to be in the same header.

  • The Authorization header should be part of the Access-Control-Expose-Headers header to allow clients access to the Authorization header content (the token);

  • Signature Algorithm: To encrypt the token we will use the default algorithm HS512.

Current User request

To provide to the client with the current user data our application should expose a service to return the user details. In this case the Authentication has a method called getCurrentUser which will return the user data. We only need register it at express.

app.get('/mythaistar/services/rest/security/v1/currentuser', auth.getCurrentUser);

Authorization

We need to secure three services, that only should be accessible for users with role Waiter:

  • (POST) /mythaistar/services/rest/bookingmanagement/v1/booking/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/filter.

To ensure this, the Authorization class has the securizedEndpoint method that guarantees access based on the role. This method can be used as middleware in secure services. As the role is included in the token, once validated we will have this information in the request and the middleware can guarantee access or return a 403 error.

app.use('/mythaistar/services/rest/ordermanagement/v1/order/filter', auth.securizedEndpoint('WAITER'));
app.use('/mythaistar/services/rest/ordermanagement/v1/order/search', auth.securizedEndpoint('WAITER'));
app.use('/mythaistar/services/rest/bookingmanagement/v1/booking/search', auth.securizedEndpoint('WAITER'));