Building a RESTful Blog APIs using python and flask — Part 1

Aladeusi Olawale A
16 min readJul 3, 2018

--

To be a programmer is to develop a carefully managed relationship with error. There’s no getting around it. You either make your accommodations with failure, or the work will become intolerable — Ellen Ullman

In this series, I’m going to take you through a very easy to learn path in creating RESTFUL API using Python and Flask micro-framework.

Let’s get started with some background context

WHAT IS RESTFUL API?

A RESTful API(Application Programming Interface) is an approach based on REST(REpresentational Stateless Transfer) technology. REST is an architectural style and approach to communications in web services development that defines a set of constraint and properties based on HTTP protocols which include GET, POST, PUT and DELETE. I’m not going to dive deep into the details about REST in this article, but you can read more about REST here

WHAT IS PYTHON?

Python is a programming language that lets you work more quickly and integrate your systems more effectively.

WHAT IS FLASK?

Flask is a micro web framework written in Python. It is called a micro-framework because it does not require any specific libraries or tools.

What we’ll build?

First, solve the problem. Then write the code — JOHN JONHSON

In this series, we’re going to develop a blog RESTFul API service, which will allow all the four basic CRUD(Create, Read, Update and Delete) operations.
The service will have the following endpoints(An endpoint is a URL pattern used to communicate with an API);

  • POST /api/v1/users — create a user(CREATE)
  • GET /api/v1/users — get all registered users(READ)
  • GET /api/v1/users/<user_id> — get a user(READ)
  • GET /api/v1/users/me — get my info(READ)
  • PUT /api/v1/users/me — update my account(UPDATE)
  • DELETE /api/v1/users/me — Delete my account(DELETE)
  • POST /api/v1/blogs — Create a blog post(CREATE)
  • GET /api/v1/blogs — Get all blog post(READ)
  • GET /api/v1/blogs/<blog_id> — Get a single blog post(READ)
  • PUT /api/v1/blogs/<blog_id> — Update a blog post(UPDATE)
  • DELETE /api/v1/blogs/<blog_id> — Delete a blog post(DELETE)

Pre-requisites

Make sure you have the following installed on your system

  • Python3.x — We’ll use python 3.XX for the blog service project
  • PostgreSQL — We’ll use postgreSQL relational database for this project
  • Pipenv — we’ll use pipenv to create and manage our project virtual environment and to also install and uninstall packages

Run the following commands to check if you have the above installed on your system

$ python --version 
$ which psql
$ pipenv --version

You should see something similar to this

Setting up project virtual environment using pipenv

Install pipenv if you don’t already have it installed on your system using pip or if you’re using a mac system using brew

$ pip install pipenv

OR

$ brew install pipenv

Let’s create our project’s directory and name it blog_api
open your terminal and run the following command to create blog_api directory

$ mkdir blog_api

Change your working directory to the directory you just created above and run pipenv command to setup project virtual environment

$ cd blog_api
$ pipenv --three

Running pipenv --three will create a virtual environment if not already created using python3 - Read pipenv documentation here to learn more about how it works

NOTE: we will be using python 3.xx in this project

So you may ask, why do we need a virtual environment in Python? using a virtual environment for python projects allows us to have an isolated working copy of python which gives us the opportunity to work on a specific project without worry of affecting other projects.

Activate the project virtual environment with the following command

$ pipenv shell

Installing Project Dependencies

The following are the required package dependencies we’ll use to develop our blog API service

  • flask — Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions
  • flask sqlalchemy — flask wrapper for Sqlalchemy, it adds Sqlalchemy support to flask apps
  • psycopg2 — python postgresql adapter that provides some commands for simple CRUD queries and more on postgresql db
  • flask-migrate — flask extension that handles SQLAlchemy database migration. Migration basically refers to the management of incremental, reversible changes to relational database schemas
  • flask-script — provides an extension for managing external scripts in flask. This is needed in running our db migrations from the command line
  • marshmallow — marshmallow is a library for converting complex datatypes to and from native Python datatypes. Simply put it is used for deserialization(converting data to application object) and serialization(converting application object to simple types).
  • flask-bcrypt — we’ll use this to hash our password before saving it in the db — of course, you don’t want to save user’s password directly into your db without hashing it
  • pyjwt — python library to encode and decode JSON Web Tokens — We will use this token in verifying user’s authenticity

Run the following command to install all the dependencies;

$ pipenv install flask flask-sqlalchemy psycopg2 flask-migrate flask-script marshmallow flask-bcrypt pyjwt

currently, your project directory should look like this

blog_api/
|-Pipfile
|-Pipfile.lock

Pipfile contains information about your app including all the app prod and dev dependencies.

Your Pipfile should contain the following;

File structures

Create the following file structure for your project
NOTE: Do not change Pipfile and Pipfile.lock, leave it as it is

blog_api/
|-src
|- __init__.py
|- shared
|- __init__.py
|- Authentication.py
|- models
|- __init__.py
|- UserModel.py
|- BlogpostModel.py
|- views
|- __init__.py
|- UserView.py
|- BlogpostView.py
|- app.py
|- config.py
|- manage.py
|- run.py
|- Pipfile
|- Pipfile.lock

Create app db

Let’s create our db and name it blog_api_db
You can use createdb command

$ createdb blog_api_db

OR create your db using any PostgreSQL client application e.g Postico, pgadmin etc.

Flask Environment Configuration

Now, let’s set up the configurations that would be required by our application.

Add the following inside /src/config.py

What we did?
We created two classes Development — which contains development environment configs and Production — which contains production environment configs. For this project, I don't want to go too complex with these configurations, so I only specified DEBUG, TESTING, JWT_SECRET_KEY, and SQLALCHEMY_DATABASE_URI options, you can read more about handling flask configurations and others here.
For our blog service, there're some values we don't want to expose to the outside world, one of it is JWT_SECRET_KEY which we will use later to sign user's token. This should be a secret key and as such should only be known to us - for this reason and more those secret variables need to be set in our system environment. You'll notice we did JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY') in our config file, we imported os and use it to get JWT_SECRET_KEY value from the system environment.

Run the following from your terminal to set system environment variables

$ export FLASK_ENV=development
$ export DATABASE_URL=postgres://name:password@houst:port/blog_api_db
$ export JWT_SECRET_KEY=hhgaghhgsdhdhdd

Note: change the DATABASE_URL value to your database url, also change JWT_SECRET_KEY value to whatever you want to use as your secret key, you'll need this later to sign in user's token.

Create App

Let’s create our app with a sample API endpoint, add the following in src/app.py file

What we did?
create_app function takes in env_name(env_name is the name of the environment we want to run our flask app on, it can be testing, development or production), this is needed to load our environment configuration. Don't forget we had already set up our environment configuration object in src/config.py file. create_app returns a new flask app object. We also set up a simple endpoint / with HTTP GET method, a request to this endpoint will return Congratulations! Your first endpoint is working.

Now that we have created our app, let create an entry point for our app

add the following code to /run.py file in the project root directory. Create run.py file in the project root directory if you don't have it already.

What we did?
Here we imported create_app() function from src/app and call the function by passing our environment name(in this case "development"). Don't forget that create_app() functions return a flask object, in this case, we name the object app. Before we called create_app() function, we added a condition that checks if the python file name is the same as the file main name, if they're the same we run our app by calling flask run() method and if not we do nothing.

Now let’s run our app

We can achieve this by running the below command from the terminal

$ python run.py

You should get something similar to below if all went well

* Serving Flask app "src.app" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 300-081-843

Note that the app is running on port 5000, that is the default port number from flask. You can change the default port number to whatever you want by passing port parameter to the run() method - in our run.py code just change app.run() to app.run(port=<new_port_number_here>).

Now let see if our sample endpoint is working

Run http://127.0.0.1:5000/ on your browser

If all went well you should see “Congratulations! Your first endpoint is working” from the browser

Database Models

A model determines the logical structures of a database. Simply put, it determines how tables would look like on the database. Models define how records can be manipulated or retrieved in the database.

We’ll create two models for our project

  • User Model and
  • Blogpost Model

Our models class will inherit Model object from flask-sqlalchemy, Flask Sqlalchemy is a flask wrapper for Sqlalchemy. Sqlalchemy is an ORM(Object Relational Mapper). An ORM is a database sql abstraction that makes it easy to carry out sql operations on relational database. With ORM, instead of writing raw sql queries (e.g to retrieve all rows from our User's table) we could do something like - model.query.all().

Add the following to src/models/__init__.py

#src/models/__init__.py
from flask_sqlalchemy import SQLAlchemy
# initialize our db
db = SQLAlchemy()

What we did?
Here we imported SQLAlchemy from flask_sqlalchemy and initialize our db

Next, let create User and Blogpost Model
User Model will have the following fields

* id
* name
* email
* password
* created_at
* modified_at

While Blogpost Model will have the following fields

* id
* title
* contents
* owner_id
* created_at
* modified_at

Add the following code to src/models/UserModel

What we did?

  • We imported db instance from src/models/__init__.py
  • Our UserModel class inherited from db.Model
  • we named our table users - __tablename__ = 'users'
  • We defined user’s table columns and assigned rules to each column, we assigned primary_key rule to id, unique rule to email field, and set not null rule to email, password and name, those fields are compulsory.
  • save() method will be use to save users to our db
  • update() method will be use to update our user's record on the db
  • delete() method will be use to delete record from the db
  • we added three additional static methods get_all_users(), get_one_user() and get_user_by_email(v) - as their names depicts to get all user from the db and to get a single user from the db using primary_key field and email field
  • __repl__() will return a printable representation of UserModel object, in this case we're only returning the id
  • __init__() is the class constructor and we used it set the class attributes

One more thing to add to our user’s model, we need to hash user’s password before saving it into the db. Why do we need to hash user’s password? Because we don’t want to save raw user’s password to the db for security reason. This is where we will use flask-bcrypt module we installed in Installing Project Dependencies

Add the following to /src/models/__init__.py

from flask_bcrypt import Bcrypt
#######
# existing code remains #
#######
bcrypt = Bcrypt()

What we did?
Here we imported Bcrypt from flask_bcrypt and intialize it in this src/models/__init__.py file.

Next, we need to make some changes to password field in our UserModel

Add the following to src/models/UserModels.py

What we did?
We made changes to UserModel to allow hashing of user’s password,

  • We added two new methods to UserModel __generate_hash() and check_hash(), we'll use __generate_hash() to hash user's password before saving it into the db while check_hash() will be use later in our code to validate user's password during login.
    Noticed we also set self.password = self.__generate_hash(data.get('password')) in our class constructor, this will make sure we generate hash for passwords before saving them into db. We also added a condition to our update() method that checks a user wants to update their password and hash it if yes.

Now, let’s create our blogpost model

copy the following to src/models/BlogpostModel.py

What we did?

  • We imported our db instance from src/models/__init__.py
  • our BlogpostModel class inherited from db.Model
  • we named our table blogposts - __tablename__ = 'blogposts'
  • We defined our blogpost’s table columns and assigned rules to each column, we assigned primary_key rule to id, and set not null rule to title, and contents, of course we don't want those fields to be empty.
  • save() method will be used to save blogpost to our db
  • update() method will be used to update our blogpost's record on the db
  • delete() method will be used to delete a record from blogpost's table on the db
  • we added two static method get_all_blogposts() and get_one_blogpost(), as their names depicts to get all blogposts and to get a single blogpost from the db
  • __repl__() will return a printable representation of BlogpostModel object, in this case, we're only returning the id
  • __init__() is the class constructor and we use it set the class attributes

Now, we need to define the relationship between our two tables users and blogposts table. A user can have many blogpost, so the relationship between users table and blogposts table will be one-to-many.
We also need to define our model's schema, remembered we installed marshmallow from Installing Project Dependencies section, here is where we'll use it.

let’s update our UserModel and BlogpostModel, add the following

UserModel

BlogpostModel

What we did?

  • We added owner_id field to BlogpostModeland we linked it to our users' table with a ForeignKey. blogposts field was added to UserModel with some rules that will allow us to get every blogposts owns by a user when querying UserModel.

Next, we need to wrap db and bcrypt with app
Update /src/app.py with the following code

We import db and bcrypt from src/model and initialized it

Migrations

Database migration refers to the management of incremental, reversible changes to relational database schemas. Simply put, migrations is a way of updating our db with the changes we made to our models. Since we had already set up our models, now we need to migrate our models changes to the db.

Add the following code to /manage.py

What we did?

We set up a migration script that will help in the maintenance of our migrations.

The Manager class keeps track of all the commands and handles how they are called from the command line. The MigrateCommand contains a set of migration commands such as init, migrate, upgrade etc. we will use these commands now.

Next, let’s run migrations initialization with db init command

$ python manage.py db init

You should see the following after running the above script

Creating directory /<path>/blog_api/migrations ... done Creating directory /<path>/blog_api/migrations/versions ... done Generating /<path>/blog_api/migrations/script.py.mako ... done Generating /<path>/blog_api/migrations/env.py ... done Generating /<path>/blog_api/migrations/README ... done Generating /<path>/blog_api/migrations/alembic.ini ... done Please edit configuration/connection/logging settings in '/<path>/blog_api/migrations/alembic.ini' before proceeding.

This will create a new migration sub-folder in the project directory

Now, run this script which will populate our migrations files and generate users and blogposts tables with our model changes

$ python manage.py db migrateINFO [alembic.runtime.migration] Context impl PostgresqlImpl. INFO [alembic.runtime.migration] Will assume transactional DDL. INFO [alembic.autogenerate.compare] Detected added table 'users' INFO [alembic.autogenerate.compare] Detected added table 'blogposts' Generating /<path>/blog_api/migrations/versions/e23b7256bd81_.py ... done

Next, apply the changes to the db by running the following command

$ python manage.py db upgrade

To confirm if the tables were created in the db, you can use psql command, check this out

User API

Our User API will have all the four CRUD operation,
We’ll start by creating create user endpoint. When a user creates an account, we want to return back to the user a JWT token that will be used for any authentication request.
Let's jump back to our code

What we did?

  • We imported request - this contains all the request info from the user including headers, body, and other information, json - to serialize JSON output, Response - we need this to build our response object and Blueprint- we need this to group our user resources together from flask.
  • We created a blueprint app that we’ll use for all user resources and we name it user_api
  • We created create() with user_api.route() operator, with this operator we defined our create endpoint as / and POST request type. create() will only accept a POST request and returned back JWT token if the request was successful. We use this UserModel.get_user_by_email(data.get('email')) to check if the email that the user supplied already exist in our db, if the query returns an empty object, that means that email does not exist and will go ahead and save the user in the db. if the user does exist, we returned back an error message telling the user to use another email. Remembered we defined our email field to be unique in UserModel
  • request.get_json() to get the JSON object from the body of the request, we use this user_schema.load(req_data) to vaidate and deserialize input json data from the user, remembered we defined UserSchema class in our UserModel model.
  • We also imported Auth from .shared.Authentication, we use Auth to generate user's token and it will be use later to decode user's token.

Next, let define Auth class.

What we did?

  • We imported jwt and set up generate_token static method that takes in user_id and use that in our payload to generates JWT token. We also set the token exp date to be 1 day after it's creation. We use JWT_SECRET_KEY already set in our system environment to sign the token with HS256 algorithm.
  • We use decode_token() static method to decode supplied user's token using the same JWT_SECRET_KEY we used in signing the token. The method checks and validates the token, we'll need this later in our project.

Next, let’s set up another route to get all user’s in the db. We want to do this in such a way that only registered user can get all users, meaning a user without an auth token cannot access this route. A user can only get a token when they create an account(this will only happen once) and subsequent login to the service. To achieve this we need to set up a decorator, let's call it auth_required, we also need to set up user login route.

  • auth_requred decorator
    let’s go back and edit our Authentication class to include auth_decorator

What we did?

  • We added a new static method auth_required to Authentication class and we wrapped it using wraps imported from python functools. we set up a condition that checks if api-token exist in the request header, a user will need to attached the token gotten from creating an account or the one gotten from logging in. If the token exist from the request header, we passed the token to decode_token method to validate the authenticity of the token, if the token is valid, we get the payload data which is the user_id and save it to g, g is a global variable in flask that is valid till we finished processing a request. We will use that later to get current user's information. We returned back to the user an error message if the token is not valid.
  • Login endpoint — We need to set up this here so that a user can get a token

What we did?

  • We added a new method login and assigned /login route with request type POST to user view. request.get_json() was used to get the request body data and we passed it to our user_schema.load(req_data, partial=True) to deserialized and validate the content of the data, we passed in partial=True to our schema since login does not require name field which is required if a user wants to create an account. Check here to read more about how marshmallow schema works.
  • We use UserModel.get_user_by_email(data.get('email')) to filter the user's table using the user email address and return an error message if the user does not exist. If the user does exist in the db, we added a condition to validate the supplied password with the user's saved hash pasword using user.check_hash(data.get('password')), remembered we added check_hash() method to our UserModel when we created it. If the password matches, then we generate and return a token to the user. The token will be used in any subsequent request.

Now, let jump back to creating the endpoint that will get all user’s data on the db and only a user with a valid token will be able to access this route.

What we did?

  • Set up a new method get_all with endpoint / and request method GET, we use UserModel.get_all_users() to query user's table on the db and returned all user's data. We also attached @Auth.auth_required decorator to validate the authenticity of the user making the request.

Finally, to complete our user APIs, let add get — GET, update - PUT and delete - DELETE a single user endpoints.
Note: A user can only update and delete their own account.

What we did?

  • We added four more endpoints to get, update and delete the current user, and the fourth one to get a user information through their id.

Finally, let’s register user_api blueprint in app, as it is currently, app does not know that user_api blueprint exist.
To register it, add the following to app.py

What we did?
We imported user_api as user_blueprint from .views.UserView and registered the blueprint using app.register_blueprint(user_blueprint, url_prefix='/api/v1/users') with prefix /api/v1/users and the blueprint name.

RUN

let’s run our code before we complete this part.
To test the endpoint you can use POSTMAN or any other app

  • Create User POST api/v1/users
  • Login POST api/v1/users/login
  • Get Me GET api/v1/users/me
    copy the jwt-token returned from the login call and put it in this request header api-token as key
  • Edit Me PUT api/v1/users/me
    using the same token as above
  • Get all users GET api/v1/users
    using the same token

And finally, delete DELETE api/v1/users/me

CONCLUSION

We’ve covered quite a lot on how to create a simple RESTful API that has the four basic CRUD operation with Authentication using Flask. We learned about configuring our Flask environment, creating models, making and applying migrations to the DB, grouping resources using flask blueprint, validating the authenticity of a user using JWT token and we also complete setting up all our user’s endpoints
We were able to set up the following endpoint;

  • Create User — POST api/v1/users
  • Login User — POST api/v1/users/login
  • Get A User Info — GET api/v1/users/<int:user_id>
  • Get All users — GET api/v1/users
  • Get My Info — GET api/v1/users/me
  • Edit My Info — PUT api/v1/users/me
  • DELETE My Account — DELETE api/v1/users/me

In Part 2 of this Series, we’ll complete setting up blogpost endpoint. Click here to checkout the project on Github.

Feel free to drop your questions if any, or drop a comment, I’ll be happy to respond to them.

Originally published at www.codementor.io.

--

--

Aladeusi Olawale A

I solve problems, I write code to implement the solutions and I ensure software qualities