Articles
Articles on web development
How I work!
Object oriented data modeling - Part 3
Creating the API
Creating routes
Now that I have created the database I can start on the API. There are many ways to create the API routes. I use nest.js as my favourite framework for building API’s.4 It's built on top of Node.js and Express.js. I won’t go through how to use Nest.js here and now. There is good resources out there to teach you more about this framework5 Basically the API routes need to send a payload corresponding to the database tables.CREATE TABLE
IF NOT EXISTS ADDRESS_BOOK (
id SERIAL PRIMARY KEY,
name VARCHAR(20),
address VARCHAR(255),
postalCode INT,
city VARCHAR(255),
country VARCHAR(255)
);
The payload for the above table would contain all the columns from the table as a JSON object. The id will not be part of the payload as it will be generated automatically when the data is saved to the table.
For Visual Studio Code, my favourite code editor, there is a plug in I use to simplify testing HTTP requests6
POST is the kind of route that sends data to the database. Afterwards you get a response like this:
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Jan 2025 17:38:26 GMT
Content-Length: 545
Connection: close
{
"message": "Not authorized.*"
}
In this case the payload wasn’t saved as the response was a 401 Unauthorized. That means I didn't have permission to use the route.
If everything goes as planned I get a response back that can look like this:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Tue, 14 Jan 2025 17:37:15 GMT
Content-Length: 545
Connection: close
API design considerations.
There are many things to take into consideration when designing an API. First I need to decide what routes I need in the first place. Most API’s use CRUD operations. CRUD means Create, Retrieve, Update or Delete. So now I have to find out how I want to use the data in the database and create a route for each using either POST, GET, PUT or DELETE requests.Create
POST adds a record to the table. Here I have to add a payload with the data I want to add to the database. The payload is usually a JSON object. The route will be defined with an endpoint like /address_book using the POST method. This is how the route will look like:POST http://localhost:8080/address_book
// { payload goes here ... }
If I want to be able to add extra emails or phone numbers to an address book entry I need the id of the entry I want to add them to. Here I will use a dynamic route. Dynamic routes
A dynamic route is a route where some of the endpoints can change. The changing part of the route is defined like :addrBookEntryId. The colon is what makes the route dynamic. The endpoint looks like /address_book_emails/:addrBookEntryId.The two routes will look like this:POST http://localhost:8080/address_book_emails/1
POST http://localhost:8080/address_book_phones/1
Here I’m trying to add a record to a table where the relational id is 1. The addrBookEntryId in the ADDR_BOOK_EMAILS and ADDR_BOOK_PHONES tables needs to have an id of a record in the ADDRESS_BOOK table.
So before the payload will be added to the database table I have to check that the provided id also is an id of a record in the ADDRESS_BOOK table. If not, the route has to respond with an error. Retrieve
GET can be used to retrieve all records or a single record in a table. The single record will be defined by a dynamic route with an endpoint /address_book/:id. I use /address_book/ if I want all records in the table. Here I also need to think about how I will retrieve the data from the ADDR_BOOK_EMAILS and ADDR_BOOK_PHONES tables. I can make a route that combines all three tables or I can make a route for each table in the database. In this case I will make a route combining all three tables because I don’t need to show only email or phone numbers. With the GET method the routes look like this:GET http://localhost:8080/address_book/1 // Get single row from db-table
GET http://localhost:8080/address_book // Get all rows from db-table
Update
PUT updates a record. Here I need an id of the row I want to update. Without the id I will update the whole table with the same data. That’s not what I want to do... Here I again use a dynamic route with an endpoint like this /address_book/:id.Here I have to provide a new payload with the data that will be updated and the id of the record I want to update. The route for the ADDRESS_BOOK table looks like this:PUT http://localhost:8080/address_book/2
In this case I want to update the row with id 2 in the ADDRESS_BOOK table if it exists. Here I again have to validate to see if the id exists in the database table.
The route for the ADDR_BOOK_EMAILS and ADDR_BOOK_PHONES tables will look like this:PUT http://localhost:8080/address_book_emails/1
PUT http://localhost:8080/address_book_phones/1
Here I again need to make sure the provided id is an id of a row in the table. If not, the route has to respond with an error. I don’t use the value of the addrBookEntryId when I want to update an email or a phone number. If I do, I update all entries for a given address book entry with the same data. I use the unique id of the row instead. It’s the column in the tables that are auto incremented.Delete
DELETE will remove a record with a given id from the database table. Here I will add a dynamic route with an endpoint like /address_book/:id. Here I only need the id of the row I want to delete, no need to add a payload as no data will be added to the database. The route will look like this:DELETE http://localhost:8080/address_book/3
When I delete a record in the ADDRESS_BOOK table I also need to remove records in ADDR_BOOK_EMAILS and ADDR_BOOK_PHONES tables if any. There is a risk that I only get to delete the ADDRESS_BOOK entry and not the data in the other tables. That could create data without relations to each other and clutter up the database.
I’m usually not deleting anything from the database. Instead I use some kind of state that can be true or false.
In all the database tables I add a column where I can set the state. Then when I want to “delete” a record in the database I update the state of the row and set it to false. In the GET routes I then search for all the rows where the state is true. If I was to search for a single record I would only get a record back if there was a record with the given id AND its state was TRUE. If one of them doesn’t match, the response will be empty. Foot notes
4.
5.
6.