This is a guest blog post from Chris Hart of Brandicted – a technologist from the great city of Montreal.
Foreword
This tutorial is meant for beginners. If you get stuck along the way, try to power through and it will probably click. If there’s anything you just don’t get or want some help with, email info@brandicted.com or leave a comment below.
Intro
Making an API can be a lot of work. Developers need to handle details like serialization, URL mapping, validation, authentication, authorization, versioning, testing, databases, custom code for models and views, etc. Services like Firebase and Parse exist to make this way easier. Using a Backend-as-a-Service, developers can focus more on building unique user experiences.
Some drawbacks of using third party backend providers include a lack of control over the backend code, inability to self-host, no intellectual property, etc.. Having control over the code and leveraging the time-saving convenience of a BaaS would be ideal, but most REST API frameworks in the wild still require a lot of boilerplate. One popular example of this would be the awesomely heavy Django Rest Framework. Another great project which requires way less boilerplate and makes building APIs super easy is Flask-Restless (highly recommended). We wanted to get rid of all boilerplate though, including the database queries that would normally need to be written for views.
Enter Ramses, a simple way to generate a powerful backend from a YAML file (actually a dialect for REST APIs called RAML). In this post we’ll show you how to go from zero to your own production-ready backend in a few minutes.
Want the code? Ramses on Github
Bootstrap a new product API
Prerequisites
We assume you are working inside a fresh virtual Python environment, and are running both elasticsearch and postgresql with default configurations. We use httpie to interact with the API but you can also use curl or other http clients.
If at any time you get stuck or want to see the final working version of the code for this tutorial, it can be found here.
Scenario: a factory to make (hopefully) delicious pizzas
We want to create an API for our new pizzeria. Our backend should know about all the different toppings, cheeses, sauces, and crusts that can be used and the different combinations of them that go into making various pizza styles.
1 2 |
|
The installer will ask which database backend you want to use. Pick option “1” to use SQLAlchemy.
Change into the newly created directory and look around.
1
|
|
All endpoints will be accessible at the URI /api/endpoint-name/item-id. The built-in server runs on port 6543 by default. Have a read through local.ini and see if it makes any sense. Then run the server to start interacting with your new backend.
1
|
|
Look at api.raml to get an idea of how endpoints are specified.
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 29 |
|
As you can see, we have a resource at /api/items which is defined by the schema in items.json.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Data modeling
Schemas!
Schemas describe the structure of data.
We need to create them for each of the different kinds of ingredients that we will make our pizzas with. The default schema from Ramses is a basic example in items.json.
Since we’re going to have more than one schema in our project, let’s create a new directory and move the default schema into it to keep things clean.
1 2 3 |
|
Rename items.json to pizzas.json and open it in a text editor. Then copy its contents into new files in the same directory with the names toppings.json, cheeses.json, sauces.json, and crusts.json.
1 2 3 4 5 6 7 |
|
In each new schema, update the value of the "title"
field for the different kinds of things that are being described (e.g. "title": "Pizza schema"
, "title": "Topping schema"
etc.).
Let’s edit the pizzas.json schema to hook up the ingredients that would go into a given style of pizza.
After the "description"
field, add the following relations with the ingredients:
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 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Relations 101
We need to do the same for each of the ingredients to link them to the pizza style recipes that call for them. In toppings.json and cheeses.json we need a "foreign_key"
field pointing to the specific pizza style that each topping would be used for (again, put this after the "description"
field):
1 2 3 4 5 6 7 8 9 10 11 |
|
Then in both sauces.json and crusts.json we do the reverse (by specifying "relationship"
fields instead of "foreign_key"
fields) because these two ingredients are being referenced by the particular instances of the pizza styles that call for them:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
For crusts.json just make sure to set the value of "backref_name"
to "crust"
.
One thing to note here is that only a crust is really required to make a pizza if you think long and hard about it. Maybe we’d have to call it bread at that point, but let’s not get too philosophical.
Also note that we have two different “directions” of pizza-to-ingredient relationships going on. Pizzas have many toppings and cheeses. These are “One (pizza) to Many (ingredients)” relationships. Pizzas only have one sauce and one crust though. Each sauce or crust may be called for by many different pizza styles. When talking about pizzas, we say there is a “Many (pizzas) to One (sauce/crust)” relationship. Whichever “direction” you want to call it by is only a matter of the entity you are talking about as a point of reference.
One-to-Many relationships have a relationship
field on the “One” side and a foreign_key
field on the “Many” side, e.g. pizzas (as described in pizzas.json) have many "toppings"
:
1 2 3 4 5 6 7 8 9 10 11 |
|
…and each topping (as described in toppings.json) is called for by certain specific pizzas ("pizza_id"
):
1 2 3 4 5 6 7 8 9 10 11 |
|
Many-to-One relationships have a foreign_key
field on the “Many” side and a relationship
field on the One side. That’s why toppings have a foreign_key
field pointing to specific pizzas, and pizzas have a relationship
field pointing to all their toppings.
Backref & ondelete arguments
To learn about using relational database concepts in detail, refer to the SQLAlchemy documentation. Very briefly:
A backref
argument tells the database that when one model is referenced by another, the “referencing” model (which has a foreign_key
field) will also provide access “backwards” to the “referenced” model.
An ondelete
argument is telling the database that when the instance of a referenced model is deleted, to change the value of the referencing field accordingly. NULLIFY
means that the value will be set to null
.
Creating endpoints
At this point, our kitchen is almost ready. In order to actually start making pizzas, we need to hook up some API endpoints to access the data models we just created.
Let’s edit api.raml by replacing the default “items” endpoint for each of our resources like so:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
|
Notice the order of endpoint definitions. /pizzas
is placed after /toppings
and /cheeses
because it relates to them. /sauces
and /crusts
are placed after /pizzas
because they relate to it. If you get any kind of errors about things missing or not being defined when starting the server, check the order of definition.
Now we can create our own ingredients and pizza styles!
Restart the server and get cooking.
1
|
|
Let’s start by making a Hawaiian style pizza:
1 2 |
|
1 2 |
|
1 2 |
|
1 2 |
|
1 2 |
|
1
|
|
Voila!
Here it is in all its greasy glory:
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 29 30 31 32 33 34 35 36 37 38 39 |
|
Seed data
The last step for bonus points is to import a bunch of existing ingredient records to make things more fun.
First create a seeds/
directory inside the pizza_factory project and download the seed data:
1 2 3 4 5 6 |
|
Now, use the built-in post2api script to load all the ingredients into your API.
1 2 3 4 |
|
You can now list the different ingredients easily.
1
|
|
Or search for the ingredients by name.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
|
So, let’s make one last pizza by finding the ingredients. How about a vegetarian one this time?
Maybe a bit of spinach, ricotta, sun-dried tomato sauce, and a whole wheat crust. First we find our IDs (yours may be different)..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Bake for 0 seconds, and..
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 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Bon appétit!
Check out the full documentation for Ramses on Readthedocs, and the somewhat more advanced example project on Github.