In this tutorial we will build a simple REST API with the Luxon framework. We will start small and slowly add functionality including authentication. Luxon provides many powerful tools that will make every step of the process more convenient.
If you have not already installed Luxon you will need to do so. (Installation instructions here)

In order to have a fully functioning project with a webserver and a database we need to create a python package that we can install as a pip library. Then we will be able to deploy the package as a project, set up the database and launch the webserver. Luxon makes this process very convenient.

We will create the package in a development directory and then we will deploy the project in an app directory. All the source code will live in the development directory and the application will be launched from the app directory. We will first create a basic python package and then deploy it as soon as it’s done. We will install the package in such a way that we can keep working on the source code and see those changes when we launch the webserver in the project. Then we can build the package piece by piece and test it along the way.

So on to the package, let’s call it myapi:

Create a working directory where we can develop the package, the actual code will go in an nested directory with the same name :

$ mkdir myapi
$ cd myapi
$ mkdir myapi

In order to install the package we need a setup.py file in the top directory:

The frommyapiimportviews line imports a module that does not yet exist, this will cause an error if we try to start the a webserver after we have installed our package. Fear not, we will write the module which is imported here in the next step. The reason we put that line in now already is because when we deploy our package with Luxon, Luxon will copy the wsgi.py file from the package dir into the project dir and we don’t want to edit any of the project code after deployment, only the package code. So we make sure the package has everything that we will eventually need.

Now we can finally install our package! We will use pip’s -e switch which will install it with an egg link, this will allow us to edit the source code after the installation.

Now that we have our package installed as python library and we can deploy it as we would on a server.

Let’s create a project directory named app next to our myapi package directory, in the app directory we will make another myapi directory in which to deploy myapi:

$ cd ..
$ mkdir app
$ cd app
$ mkdir myapi

Everything is now set up for us to deploy our package with Luxon:

$ luxon -i myapi myapi

This does a number of things, it copies over the policy.json, settings.ini, and wsgi.py files from the
package directory as well as creating templates and tmp directories inside myapi. The tmp directory
is where all the session data will live. The templates directory is where servable html templates will live.
Neither of these directories will be relevant in this tutorial. We won’t actually write any code in the project
directory, all of that will still happen in the package directory. We will however launch the webserver from the
deployment directory.

We almost have everything we need to launch a webserver that can serve dynamic Python content. Except of course
the webserver itself. We will use Gunicorn.

$ pip3 install gunicorn

We can’t yet test if our project was successfully deployed however because we still need to create the views module
which the wsgi.py file imports. Just hang on, by the end of the next step we will be able to launch a webserver that
responds to a call on the homepage.

We are simultaneously using two directories, the package and the project. We will mostly be working in the package
directory to write code but we will be going back to the project directory to start the server, set up the database etc.
Make sure not to get confused between the two. Before we move on let’s clarify what the directory structure looks like
at this point:

Now we can start building our API by creating views/resources. The views will exist as their own module in the package.
The views module will consume and respond to every call made to our API. The views will import all the code they need
from the rest of the myapi modules as needed. Let’s create the module in our package directory at: myapi/myapi

mkdirviewstouchviews/__init__.py

To start off we will create a simple view that will respond to a “GET” request to the homepage “/”.

To create the view we defined a function that returns the resource we need. Then we decorated the function with Luxon’s
powerful register module which attaches the function to a specific request method, GET in this case, and a Location,
root “/” in this case. The req and resp arguments for this function is luxon’s WSGI Request and
Response objects, respectively.

For this view to be usable we need also need to import it in the views/__init__.py file:

importmyapi.views.homepage

We can now finally use Luxon to start the webserver on our local host, with port 8000. Remember that we want to
execute this command in the terminal open in our app directory.

A model is a useful data structure that Luxon can use to automatically create/update databases. You can read more about
models here.

The models we create will live in their own module, same as the views. In this module we will create a user.py file
to house our user model. Let’s create the module in our package directory at: myapi/myapi

mkdirmodelstouchmodels/__init__.pytouchmodels/user.py

The model can have any number of members with highly specific fields provided by Luxon (full list
here). In this case we will keep it simple. We’ll give our users a username, password, role and a
universally unique identifier that will double as the primary key. Let’s implement it in our user.py file:

Now that we have a model we can write more sophisticated views to make use of it. Since we will end up having a number
of views to perform different actions with users (Create/Read/Update/Delete) we will group them together in a class.
This will work slightly differently in that we will use the register.resources method to register the views and we
will specify all the routes in the constructor. To specify the routes we will use Luxon’s router module.

We need to create another file in our package directory under views to house the users views in the myapi/myapi
directory:

$ touch views/users.py

And remember to import the new view in views/__init__.py:

importmyapi.views.homepageimportmyapi.views.users

Let’s impliment the first user view in a class called Users in our views/users.py file:

Default browsers are great for sending GET requests to our API, but we want to be able to send other kinds of requests
too. Let’s use Postman, a useful tool to test APIs.

Fire up Postman so we can create a user.

Create a POST request with “http://127.0.0.1:8000/create” in the request URL bar. Next we write the body of the
request as raw JSON. It contains all the information that we will send to create the new user:

Note that the password has been hashed. We hash the clear text password we receive from the user before we send it to
the database so that even if someone examines the user table in the database all they will see is a useless hash,
the actual password will be safe. More about password hashing in the authentication part of the tutorial.

We have already created the “create” view. The rest of the views are created in a similar way.
The /users path, which returns a view of all the users in the database, is slightly more complicated.
It requires a connection object which will execute a SQL query. Remember to import db from Luxon which will allow us
to easily create a connection object. The rest of the views are fairly straight forward, here is the complete code for
views/users.py. Note the new imports:

One thing to note is the id argument in the views that perform an operation on a specific user. This argument is taken directly from the url. To test these views, simply copy the id string of the specific user and paste it after the route in the url. For example:

Now that we have a simple API up we can start implementing some kind of authentication. Every User has a username and password which are specified upon creation. Let’s create a login view that will receive a username/password and validate it against the users in the database. Upon validation the view will return a token which can then be sent with future API calls to verify the authenticity of the user sending the calls.

Before we do anything else we have to generate RSA keys for our project, Luxon needs them for authentication. We can use Luxon to generate them in our app directory:

$ luxon -r myapi

Now lets create another file in our package directory under views to house the login views:

fromluxonimportregister,dbfromluxon.exceptionsimportAccessDeniedErrorfromluxon.utils.passwordimportvalid@register.resource(['GET','POST'],'/login')deflogin(req,resp):# get the username and password from the request objectusername=req.json.get('username')req_password=req.json.get('password')# sql query that will return the password from the database for the given usersql="SELECT id, password, role FROM user WHERE username = %s"# connection to databasewithdb()asconn:# cursor obj to execute our sql query with given usernamecrsr=conn.execute(sql,(username,))# fetch result from cursor objectresult=crsr.fetchone()ifresultisNone:raiseAccessDeniedError("User not found")# password from databasedb_password=result['password']#validate the hashed password from dadabase with given passwordifnotvalid(req_password,db_password):raiseAccessDeniedError("Wrong password")# now that the login details have been validated# we can create the user credentials which will include:# the username, user id, user role, token and token expiry timereq.credentials.new(result['id'],username)# add the user's role to the tokenreq.credentials.roles=result['role']# return the tokenreturnreq.credentials

Restart the server so we can test our login view.
To test the login we’ll create a POST request to “http://127.0.0.1:8000/login” and send the following body:

{"username":"Ricky T Dunigan","password":"hypnotizeminds"}

The password we sent matches the password for that user in the database so we get the user’s credentials back: all the
user details along with a token as well as a new “expire” field, which is the time when the token will expire. For more
about Luxon’s password hashing have a look Here

Luxon offers the ability to protect views based on users’ roles, aka Role Based Access Control (RBAC).

This is done by a tagged view with with a rule. Only users assigned
with roles that match the rule assigned to the view, can access that view.
The roles and their associated rules are defined in the myapi/policy.json file. This was created when we set up the
package:

{"role:admin":"'admin' in req.credentials.roles","role:user":"'user' in req.credentials.roles","admin_view":"$role:admin","user_view":"$role:admin or $role:user"}

These rules and roles are executed as python statements. For example, the admin_view rule will be expanded to True, if
the role:admin role expands to True. And the role:admin role will expand to True if the python statement
'admin'inreq.credentials.roles expands to True.

To protect the User views with role based access, simply add a tag as an argument to every User view in
views/users.py

Now only users with the admin role assigned to them can make calls to the create, update and delete views,
that’s to say all the views that write to the database. A user with the user role can access the views which only
read from the database. A user with the admin role can also access views with the user tag.

Right, now our API is secure. Remember to restart the server so these changes take effect.
We already have a user “Ricky T Dunigan”, with the admin role, that we can log in as. Once we logged in with him and
received a Token we can use it to access the create view. Create a POST request with “http://127.0.0.1:8000/create”
in the request URL bar same as before. All we need to do is add a header. Put “X-Auth-Token” in the key field
and paste the Token into the value field.

Create another user with a user role instead of admin and repeat the same process of adding a header to the request.