Coding a production ready API — part 1: implementing an ORM

Create a efficient, safe and fast API for our website without the need to write a single line of SQL

Coding a production ready API — part 1: implementing an ORM
Our API will fetch faster than these cute creatures (image by Chevanon Photography on Pexels)

Coding a production ready API - part 1: implementing an ORM

Imagine going to a restaurant. You sit down, look at the menu and select some dishes. Then you walk to the kitchen, tell the chef what you want to eat and wait for him to finish before taking your meal back to your table. What’s going on in this weird restaurant?

An application without an API is much like a restaurant without waiters. In restaurants you don’t have to communicate with the kitchen and wait for your order to enjoy a meal. In the same way a website doesn’t have to know how to communicate with your database for example. Like a waiter, our API receives an order from our website: ‘I’d like some user statistics with a side of meta data”. The API then checks the order (are you allowed to have that data?), convinces our database (the chef) to cook him up some tasty information, waits for the database to finish and finally returns the data to the website.

What is this article all about?

We’ll use an ORM to create a fully functioning, fast and secure API for a real-world application we’re building called BeerSnob; a site that’s specially made for drinkers of fine beers. It allows users to share reviews about beers they drank at specific venues; rating both the venue and the beer while providing information about it’s price and taste. At the end of this article you’ll have a working API with an ORM with a migration model to op it off! First we get into why we would create an API or an ORM, then we start coding.


0. Goals and preparation

Our goal is to create an API for BeerSnob that will receive user-created data packages from the website. Based on the required action it then retrieves data from the database or inserts the data package into the database. The data package may contain information for multiple tables and it’s the API’s job to make sure the right information ends up in the right table.

Why build an API?

If you click on ‘my friends’ on the Facebook website or in the app, you’ll send a request for information to an API. This piece of software translates your request, checks if you are allowed make the request, collects all the data and response with your data package. This is great for a couple of reasons:

  • Keeps our code nice and tidy
  • Secures access to our database (website is decoupled from our database e.g.: no need for our website to have database credentials)
  • Checks user access to data (I can only change my profile, not someone else’s)

Why use an ORM?

ORM stands for Object-Relational Mapping, it is a technique that allows you to query and manipulate data from a database using objects. You don’t have have to write queries anymore, you can use objects to retrieve data. Example in pseudocode: song_list = SongTable.query(artist="Snoop dogg"). The advantages:

  • There’s no need to write SQL
  • A lot of things are done automatically
  • Prevents SQL injection

Let’s code!


1. Setup

Before the API can handle database data we’ll need a database to actually work with. In this article we use migrations to create all tables, associations, associations and indices. Whether you’re using the migrations or not, the article also contains a very nice diagram of BeerSnob’s database structure.

We’ll build the API on the migration model that we’ve built in this article. If you want to code along then git clone the code from here. First let’s build an ORM and then implement our API.

2. Creating models for our ORM

The ORM is the translation between an object we can use in our API and the data in our database. In order to translate database data to an object it uses ‘models’. In our BeerSnob example we’d like to do something along the lines of foundbeers = BeerTable.Get(ID=2). In this example BeerTable is a model that is able to connect to our Beers Table in the database and perform actions on it like retrieving data and inserting new records.

Like this good boy our ORM will help us in our endeavours (image by Ryan Stone on Unsplash)

Let’s create our first model; countries. This model handles data in our countries table. Create a new file in the “models” directory called countries.js with the following content:

This simple file allows us to create a model. Creating a model and understanding how it works is not hard. The important things are defined below.

  • in the tableModel (line 5) we’ve added the modelName. I’ve decided to keep our tablenames plural (the countries table contains many records for countries) and the modelsNames’s singular (a model describes just one country).
  • The models associates with another model (line 11). We tell our ORM that if we delete a country, a city should be deleted to. Also we define the foreignKey on the country table (CountryId).
  • in Init we define some information about columns. The ORM needs to know the datatypes and the names of the columns in the database.
  • timestamps: false (line 24). If this is set to true the ORM adds additional columns that keep track of created and modified timestamps (we handled those manually but the ORM could do it for you too).

When defining associations between models there are several options. I’ve already used some in this project so far but we’ll get into them more in-depth in the next part. I’ve created models for the other tables as well. Check out the repository for more information.

3. API preparations

Before we can start creating creating the API we need to install some dependencies and prepare a few things.

3.1 Install packages.

Go to your root folder and:npm install --save express body-parser
npm install --save-dev nodemon

This will install packages that our API requires. Express is a webserver, body-parser allows us to parse the bodies of requests sent through that webserver. Then we install a dev-dependency; these are only used to build our application and are generally just for developing. Nodemon will reload our webserver every time we change our code. My f5 button is very thankful for this.

3.2 Configure your app to work with Nodemon

Open package.json, a file in your root folder. Adjust the ‘scripts’ part to reflect the following:"scripts": {
   "start": "node server.js",
   "dev": "nodemon server.js"
 },

This will allow us to either call npm run start in production environments or npm run dev to run our app with nodemon, allowing the restart.

3.3 Create folders

Create two folders in your root directory: “routes” and “service”.

4. Building the API

It’s time to put those models to work! With the API we’ll be able to receive requests and use the models to retrieve data from the database. We first design our server that will catch all requests and pass them through to a route. The route will then check the request and communicate with the database.

Time for some fetching! (image by Jozef Fehér on Pexels)

4.1 Create a server for our app

With the script below we’ll have a server up and running.

Here’s what happens:

  • We import some packages and also the loggingService. This service helps us debugging, we do so on line 13.
  • On line 9 we create our app that will pass through requests.
  • Line 24; we tell the app that we want to use bodyParser; this way we can receive e.g. json data.
  • Line 29: we set the rules of the API.
  • Line 41 till 46: we catch some URL paths and pass them to our routes.
  • Line 59: create a server for our app and tell it to listen to port 5000 on localhost

4.2 Creating routes and handling requests

Now that our server is up and running it’s time to pass requests to the routes that we’ve defined on line 41 until 46. I’ll show an example of handling a route below. I’ve implemented the other routes in exactly the same way; you’ll be able to find in our repository.

In the code above you see how the ORM works when handling a route. Lets walk through the first route (line 11 until 39).

  • line 11: we define the route. /searchmeans in our case localhost:5000/api/beers/search
  • Line 14: we get data from our url. req.query retrieves url parameters (the bold part in https://mikehuls.medium.com/?source=twitter. We store the value of parameter q in a variable that’s also called q (for query). In the url localhost:5000/api/beers/search?q=ale the value for key q is ‘ale’.
  • Line 20: Do nothing if q is too short
  • Line 23 until 30: return all beers that contain q (case insensitive)
  • Line 33 res.retun is what our API returns. It will return an object with one key: ‘beers’ and an array of all the beers we’ve found in our database. We return with status code 200 (success).
  • Line 36: if anything goes wrong in the try-block we let the client know by returning an error (we use the errorService for this).

As you see we don’t have to write a line of SQL ourself; we can just call our models that translate our request and communicate with our database. It not only is very convenient, it also prevents SQL injection.

5. Testing our routes

Let’s test our beers route! In beers there are three different paths:

  1. a GET for /search
    This route uses our ORM to query the Beers table. You are able to pass a searchword (q) like localhost:5000/api/beers/search?q=heine. It will then look for beers that are like the q (‘heine’).
  2. a GET for /
    This route takes the optional query parameter Id and tries to find a record if the Id is provided. Else it returns all beers. Try localhost:5000/api/beers or localhost:5000/api/beers?id=1
  3. a POST for /
    This route accepts a body which it will catch on line 72 (req.body). Try to POST the json below to localhost:5000/api/beers and you’ll see a new record appear in the database.{
       "Name": "Bud Light",
       "Type": "Beer flavored water"
    }

This was a pretty long article: we’ve deserved this cute puppy (image by Hannah Grace on Unsplash)

Conclusion

This API provides some nice basic functionalities for our app! Using our ORM we are now able to use our API to search for, get and create countries, cities, venues, beers, users and reports. In the next part we’ll flesh out our API even more:

  • We’ll add more in-depth associations (return all beers user X submitted or return all users that visited venue X and reported beer Y).
  • Implement more functionalities for example: users want to update their password
  • We’ll add security; I can only edit my own password, not someone else’s.

This was a long article; I hope you made until here and that my explanations wee clear enough. If the weren’t please let me know where I can improve myself. Follow me to stay tuned!

— Mike