Hi, in this series of Using TypeScript to write security APIs with Node.js and Express. , I would like to move on to the second lesson about creating Data Models and Services.
Overview of posts
- Introduction and initial installation
- Create Data Model and Services
- Create Endpoints
- API security
- Rights management
You should read each article in turn to understand the contents of the series.
Before creating controllers and services, we will define the data structure in this application. A menu item will have several possible attribute types. I will take the following example:
id
: (number) Unique identifier for each itemname
: (string) item nameprice
: (number) the price of each item is assumed in VNDdescription
: (string) describes itemimage
: (string) The URL points to the image of the item
Using TypeScript – an object-oriented language – to build the API, gives us various options to define and execute the structure of objects. We can use classes or interfaces to define them. For menu items, interfaces can be used because they are not part of the compiled JavaScript package; furthermore, for simplicity, we do not need instances of the menu item.
We will create a subdirectory named items
in the src
directory to save the files related to the menu items:
1 2 | mkdir src/items |
Model API Resources with Interfaces
To model the data, we will define an interface Item
as follows:
1 2 | touch src/items/item.interface.ts |
File content:
1 2 3 4 5 6 7 8 | export interface Item { id: number; name: string; price: number; description: string; image: string; } |
Next, define an interface Items
is a collection of Item
:
1 2 | touch src/items/items.interface.ts |
File content:
1 2 3 4 5 | import { Item } from "./item.interface"; export interface Items { [key: number]: Item; } |
Create Data Service to manipulate API Resource
Service allows packaging of related business logic (which we can share to many other projects). Thus, our application can use the service to access and manipulate records from the store.
Create module service definition file:
1 2 | touch src/items/items.service.ts |
File content:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * Data Model Interfaces */ /** * In-Memory Store */ /** * Service Methods */ |
In the Data Model Interfaces
, we will import interfaces:
1 2 3 4 5 6 7 | /** * Data Model Interfaces */ import { Item } from "./item.interface"; import { Items } from "./items.interface"; |
For simplicity, I will not create a database just to save the records. Instead, I will create some representative objects and store them in RAM:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /** * In-Memory Store */ const items: Items = { 1: { id: 1, name: "Burger", price: 5.99, description: "Tasty", image: "https://cdn.auth0.com/blog/whatabyte/burger-sm.png" }, 2: { id: 2, name: "Pizza", price: 2.99, description: "Cheesy", image: "https://cdn.auth0.com/blog/whatabyte/pizza-sm.png" }, 3: { id: 3, name: "Tea", price: 1.99, description: "Informative", image: "https://cdn.auth0.com/blog/whatabyte/tea-sm.png" } }; |
It should be noted that whenever you reset the server, the entire object represented above will be deleted from RAM. But thanks to HMR use of webpack does not need to be reset anymore, unless there is a change in the file service.
Next, we will create main CRUD methods with items. The first will be 2 methods of finding items:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * Service Methods */ export const findAll = async (): Promise<Items> => { return items; }; export const find = async (id: number): Promise<Item> => { const record: Item = items[id]; if (record) { return record; } throw new Error("No record found"); }; |
Here I use asynchronous
to simulate read and write operations in accordance with reality. Looking at the function name and its content, you can guess what the purpose of each function is.
CREATE
Next, we will create the CREATE
method and add below the find
function above:
1 2 3 4 5 6 7 8 | export const create = async (newItem: Item): Promise<void> => { const id = new Date().valueOf(); items[id] = { ...newItem, id }; }; |
The CREATE
method simply takes an argument that is an object of type Item
. This object will have properties as defined, except id
will be created in the function. And to create an unique id
, we use the value of the current date – DATE
.
UPDATE
Next, we will add the UPDATE
method right after CREATE
:
1 2 3 4 5 6 7 8 9 | export const update = async (updatedItem: Item): Promise<void> => { if (items[updatedItem.id]) { items[updatedItem.id] = updatedItem; return; } throw new Error("No record found to update"); }; |
The UPDATE
method takes an object parameter of type Item
. Here, the Object must include the id
(obviously).
DELETE
Finally, we define the REMOVE
method:
1 2 3 4 5 6 7 8 9 10 11 | export const remove = async (id: number): Promise<void> => { const record: Item = items[id]; if (record) { delete items[id]; return; } throw new Error("No record found to delete"); }; |
The REMOVE method takes a parameter as the id
of the item to be deleted.
So we have just finished creating a service module. Any other project can use this service module’s code because it is not tied to any specific framework. In the next article, we will use them to create API Controllers
It is worth mentioning that you may have used a TypeScript class to define and encapsulate logical services. However, using functions makes testing module services easier.
The article may be confusing or erroneous, please comment. Thank you for reading my post. See you in the next article on Creating Endpoints