Home TutorialsPython Build an API with Python Flask

Build an API with Python Flask

by Atif Azad

Flask is a micro framework that suits very much if you want to build a lightweight backend for your mobile apps. Not only this but it is also helpful in creating a complete website. The benefit is that it doesn’t include so many libraries out of the box. However you can plug-in libraries as needed in your project. In this tutorial, we are building API with Python Flask.

High level comparison between Flask and Django

  • If you are not an experienced Python developer, you can get started with Django faster. The reason is that out of the box Django provides all the essential components of a web app.
  • This means that the Django app is already big size with a lot of dependencies which you might even not need.
  • On the contrary, Flask has only a few dependencies. There are also a few more optional dependencies but those are not installed by default. You can install optional dependencies if needed.
  • This way Flask gives you more flexibility in choice but at the same time requires you to spend time in finding libraries that fit your need. This means that a less experienced developer may have to spend a bit more time in finding suitable libraries.

Create Virtual Environment

Install Flask

  • Create a folder for your application. Let us name it flask_api_demo/.
  • Run following command in Terminal while you are inside above created folder.
> pip install Flask

Check what dependencies have been installed

As I wrote above, Flask installs minimal dependencies. You can verify that by running following command.

> pip freeze

This command shows you the installed dependencies in your current virtual environment. The list you’d see should be like:

Click==7.0
Flask==1.1.1
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
Werkzeug==1.0.0

So only five dependencies installed other than Flask itself!

Create database

This example uses PostgreSQL database. You can use other databases supported by SQLAlchemy, for example MYSQL, SQLite etc, but you’ll have to accordingly make changes in .env and config.py files in next steps.

So go ahead, create a database and name it as you like!

Install python-dotenv and create .env file

Install python-dotenv which will enable you to create a .env file to put all your ENV variables in dev environment.

> pip install python-dotenv

While you are still at the root level of flask-api-demo/, create .env file with following content in it.

FLASK_ENV=development
FLASK_DEBUG=true

APP_BASE_URL=127.0.0.1
APP_SECRET_KEY=dev

DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=<db_name>
DB_USERNAME=<username>
DB_PASSWORD=<password>

SESSION_LENGTH=2592000

Replace <db_name>, <username> and <password> with valid values.

Add Config file

  • Create config.py under flask-api-demo/.
  • In this file, create Config class with following content.
import os
from dotenv import load_dotenv

load_dotenv()

class Config:

    ENV = os.getenv('FLASK_ENV', 'development')
    FLASK_DEBUG = os.getenv('FLASK_DEBUG', True)
    SECRET_KEY = os.getenv('APP_SECRET_KEY', 'dev')
    BASE_URL = os.getenv('APP_BASE_URL', '127.0.0.1')

    DATABASE_HOST = os.getenv('DB_HOST')
    DATABASE_PORT = os.getenv('DB_PORT')
    DATABASE_NAME = os.getenv('DB_NAME')
    DATABASE_USERNAME = os.getenv('DB_USERNAME')
    DATABASE_PASSWORD = os.getenv('DB_PASSWORD')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_DATABASE_URI = 'postgres://{username}:{password}@{host}:{port}/{db_name}'.format(
        username=DATABASE_USERNAME,
        password=DATABASE_PASSWORD,
        host=DATABASE_HOST,
        port=DATABASE_PORT,
        db_name=DATABASE_NAME
    )
    SESSION_LENGTH = int(os.getenv('SESSION_LENGTH'))

Define User and AuthToken models

  • Under app/ folder, add a new file models.py.
  • Add User model in this file.
from . import db

class User(db.Model):

    __tablename__ = 'users'

    id = db.Column(
        db.Integer,
        primary_key=True
    )

    username = db.Column(
        db.String(64),
        index=False,
        unique=True,
        nullable=False
    )

    password = db.Column(
        db.String(64),
        index=False,
        unique=False,
        nullable=False
    )

    email = db.Column(
        db.String(80),
        index=False,
        unique=True,
        nullable=False
    )

    created_at = db.Column(
        db.DateTime,
        index=False,
        nullable=False
    )

    updated_at = db.Column(
        db.DateTime,
        index=False,
        nullable=False
    )

    auth_tokens = db.relationship('AuthToken', backref='user')

    def __repr(self):
        return '<User {}>'.format(self.username)

class AuthToken(db.Model):

    __tablename__ = 'auth_tokens'

    id = db.Column(
        db.Integer,
        primary_key=True
    )

    user_id = db.Column(
        db.Integer,
        db.ForeignKey('users.id'),
        nullable=False
    )

    token = db.Column(
        db.String(64),
        nullable=False,
        unique=True
    )

    expires_at = db.Column(
        db.DateTime,
        index=False,
        nullable=False
    )

    created_at = db.Column(
        db.DateTime,
        index=False,
        nullable=False
    )

user_id field in AuthToken is a foreign key referencing the id field in Usermodel. We have also defined a backref property in User model:

auth_tokens = db.relationship('AuthToken', backref='user')

This lets us accessing the auth_tokens assigned to a User with user.auth_tokens.

Create app/ folder and __init__.py file

  • Create app/ folder under flask-api-demo/.
    (The name app is my own choice, you can name it differently if you want to).
  • Create __init__.py file under app/.
  • Add following code to __init.py.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.Config')
    db.init_app(app)

    with app.app_context():
        from . import models

        db.create_all()

    return app

Inside create_app function, we are instantiating app, a Flask app. We are also loading app.config from Config file we created above. Then we load the db instance with app configs (we instantiated db above create_app function and it will be importable in other files/modules.

With db.create_all() call, all the tables for all the models defined in models.py will get created. So far we have defined only User model, so this function call will create one table users (as the name specified using __tablename__ property of User model.

Create DB tables

Execute the following command in your terminal.

> flask run

When you run this command, the code inside __init__.py gets executed and following happens.

  1. Flask’s dev server starts serving the app on http://127.0.0.1:5000/
  2. With execution of db.create_all(), tables associated with both of our models get created.

Now you can check your database, if everything was done correctly so far, there should be two tables users and auth_tokens.

Create api.py

Now, let us build our first Python Flask API endpoints. Under app folder create api.py file and add following import statements.

import hashlib
import random
import string

from flask import request
from flask import current_app as app
from .models import db, User, AuthToken
from datetime import datetime, timedelta

User registration endpoint

In api.py, create /api/users route and allow POST method on this path with create_user function.

@app.route('/api/users', methods=['POST'])
def create_user():
    username = request.form.get('username')
    password = request.form.get('password')
    email = request.form.get('email')

    if username != None and password != None and email != None:

        response = None
        try:
            new_user = User(
                username=username,
                password=hashlib.sha256(str(password).encode('utf-8')).hexdigest(),
                email=email,
                created_at=datetime.now(),
                updated_at=datetime.now()
            )

            db.session.add(new_user)
            db.session.commit()

            response = response = {
                'status': 'success',
                'token': 'User regisrtered successfully.'
            }
        except Exception as error:
            response = response = {
                'status': 'failed',
                'token': 'User registration failed.'
            }

    return response

This function will be call when server receives a POST request on /api/users path. If all required parameters are available in request, it creates a new user. For password field, we are using sha256 hash.

Login endpoint

Create /api/login route accepting POST requests. To do so add following function in api.py.

@app.route('/api/login', methods=['POST'])
def authenticate():
    username = request.form.get('username')
    password = request.form.get('password')

    response = None
    try:
        authenticated = User.query.filter(
            User.username == username,
            User.password == hashlib.sha256(str(password).encode('utf-8')).hexdigest()
        ).first()

        if authenticated != None:

            token = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(64)])
            if len(authenticated.auth_tokens) > 0:
                token = authenticated.auth_tokens[0].token if authenticated.auth_tokens[0].expires_at <= datetime.now() \
                    else authenticated.auth_tokens[0].token
            else:

                auth_token = AuthToken(
                    user_id=authenticated.id,
                    token=token,
                    expires_at= datetime.now() + timedelta(seconds=app.config.get('SESSION_LENGTH')),
                    created_at=datetime.now()
                )
            
                db.session.add(auth_token)
                db.session.commit()

            response = {
                'status': 'success',
                'token': token
            }
        else:
            response = {
                'status': 'failed',
                'message': 'Login failed'
            }
    except Exception as error:
        response = {
            'status': 'failed',
            'message': 'An error occured: {}'.format(error)
        }

    return response

It reads username and password sent in POSTrequest and looks into DB if the username, password combination matches any existing user. If yes, it generates a token (if there is no existing valid token) for the authenticated user.

On successful authentication and token generation, it returns the success response containing a token otherwise, the response contains a failure message.

Import api.py in __init__.py

To make the routes available, import api.py under app_context in __init__.py. Now, __init__.py should look like this:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.Config')
    db.init_app(app)

    with app.app_context():
        from . import models
        from . import api

        db.create_all()

    return app

Testing the API endpoints

Use Postman or any other API client tool to test both endpoints of our Python Flask API. 🙂

Flask API - User Registration API usage in Postman
Image: User registration API (/api/users) usage in Postman
Flask API - User Login API usage in Postman
Image: Login API (/api/login) usage in Postman

Code

You can get the code from my Github repo: flask-api-example

0 0 votes
Article Rating

You may also like

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy
0
Would love your thoughts, please comment.x
()
x