-
Notifications
You must be signed in to change notification settings - Fork 126
nodejs design
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:
This structure can be shown in the following example image:
-
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
-
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.
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 });
}
});
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.
For the Authentication and Authorization the app will implement the json web token
protocol.
Refer to JWT basics for more information.
The Json Web Token pattern will be implemented based on the JSON web token
library available on npm.
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
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.
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);
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'));
-
-
Technical design
-
Data model
-
Server Side
-
Client Side
-
-
Security
-
Testing
-
Server Side
-
Client Side
-
End to end
-
-
UI design
-
CI/CD