Build RESTful APIs using Python / Flask
Tags: python, tutorial, webdev, beginners
Published: 2020-07-30 21:05:14

Python / Flask framework combination is one of the popular choices for building back-end APIs for a web application. Building an application’s back-end this way gives several options for coming up with a front-end using different technology stack. 

In this article, let’s walk through the steps involved in building a simple API for maintaining Quotes using the Flask framework with SQLite database as its datastore. 

<h3>Project Setup</h3>
Create a project folder - `mkdir quotes-api`
Setup and activate virtual environment - <a href="https://dev.to/dev0928/what-is-a-python-virtual-environment-2hkn">Virtual Environment Setup</a> 
Install below packages in project’s virtual environment: 
``` python
 pip install flask
 pip install flask-sqlalchemy
 pip install flask-marshmallow
```

<h3>Application Setup</h3>
Create a file called `app.py` in the project folder and add necessary imports, application setup code along with Marshmallow library setup like shown below:
``` python
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String
from flask_marshmallow import Marshmallow
import os


#Application and database setup
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'quotes.db')

db = SQLAlchemy(app)
# Marshmallow library is used for effective object serialization
ma = Marshmallow(app)
```

<h3>Database Setup</h3>
Let’s add below code to the `app.py` file so we could create a `quotes` database and seed data through the flask commands.  
``` python
#Database creation flask commands
@app.cli.command('db_create')
def db_create():
    db.create_all()
    print('Database created!')


@app.cli.command('db_drop')
def db_drop():
    db.drop_all()
    print('Database dropped!')


@app.cli.command('db_seed')
def db_seed():
    quote1 = Quote(quote_desc='It always seem impossible until it is done.',
                     quote_type='Motivation',
                     author='Nelson Mandela')

    quote2 = Quote(quote_desc='With the new day comes new strength and new thoughts.',
                         quote_type='Motivation',
                         author='Eleanor Roosevelt')

    quote3 = Quote(quote_desc='The secret of getting ahead is getting started.',
                     quote_type='Motivation',
                     author='Mark Twain')

    quote4 = Quote(quote_desc='With self-discipline most anything is possible.',
                     quote_type='Inspiration',
                     author='Theodore Roosevelt')

    quote5 = Quote(quote_desc='It is during our darkest moments that we must focus to see the light.',
                     quote_type='Inspiration',
                     author='Aristotle')


    db.session.add(quote1)
    db.session.add(quote2)
    db.session.add(quote3)
    db.session.add(quote4)
    db.session.add(quote5)
    db.session.commit()
    print('Database seeded!')


# database model
class Quote(db.Model):
    __tablename__ = 'quotes'
    quote_id = Column(Integer, primary_key = True)
    quote_desc = Column(String)
    quote_type = Column(String)
    author = Column(String)


# Quote model added to the Marshmallow library for JSON serialization
class QuoteSchema(ma.Schema):
    class Meta:
        fields = ('quote_id', 'quote_desc', 'quote_type', 'author')


quote_schema = QuoteSchema()
quotes_schema = QuoteSchema(many=True)

```

Execute below code in project’s virtual environment to create and seed the quotes data:
```python
flask db_create
flask db_seed 
```
<h3>CRUD Operations</h3>
Now that all of the setup part is complete, below methods added to the `app.py` file help perform the actual CRUD (Create, Read, Update and Delete) operations. Note the appropriate use of HTTP Methods and status codes to signify success or failure of a particular API endpoint.

<h3>Read Endpoint</h3>
```python
@app.route('/quotes', methods=['GET'])
def quotes():
    quotes_list = Quote.query.all()
    result = quotes_schema.dump(quotes_list)
    return jsonify(result)


@app.route('/quote_details/<int:quote_id>', methods=['GET'])   
def quote_details(quote_id: int):
    quote = Quote.query.filter_by(quote_id=quote_id).first()
    if quote:
        result = quote_schema.dump(quote)
        return jsonify(result)
    else:
        return jsonify(message="That quote does not exist."), 404
```

<h3>Add Endpoint</h3>
```python
@app.route('/add_quote', methods=['POST'])   
def add_quote():
    quote_desc = request.form['quote_desc']
    test = Quote.query.filter_by(quote_desc=quote_desc).first()
    if test:
        return jsonify(message="There is already a quote by that description."), 409
    else:
        quote_type= request.form['quote_type']
        author= request.form['author']
        quote =  Quote(quote_desc=quote_desc,
                         quote_type= quote_type,
                         author= author)  
        db.session.add(quote)
        db.session.commit()                 
        return jsonify(message="Quote added successfully!"), 201
```

<h3>Update Endpoint</h3>
```python
@app.route('/update_quote/<int:quote_id>', methods=['PUT'])   
def update_quote(quote_id : int):
    quote = Quote.query.filter_by(quote_id=quote_id).first()
    if quote:
        quote.quote_desc = request.form['quote_desc']
        quote.quote_type = request.form['quote_type']
        quote.author= request.form['author']
        db.session.commit()                 
        return jsonify(message="Quote successfully updated!")
    else:
        return jsonify(message="That quote does not exist"), 404
```    

<h3>Delete Endpoint</h3>
```python
@app.route('/remove_quote/<int:quote_id>', methods=['DELETE'])   
def remove_quote(quote_id : int):
    quote = Quote.query.filter_by(quote_id=quote_id).first()
    if quote:
        db.session.delete(quote)
        db.session.commit()                 
        return jsonify(message="Quote successfully deleted!"), 200
    else:
        return jsonify(message="That quote does not exist"), 404
```

<h3>How to test the API?</h3>
Add below code to the end of the app.py file, so we could start application using `python app.py`. API endpoints could be tested using <a href="https://www.postman.com/">Postman</a> application.
```python
if __name__ == '__main__':
    app.run()
```

<h3>Next Steps:</h3>
* Extend API to provide more endpoints
* Secure API using JWT
* Build a front-end using react.js, vue.js or any other suitable technology
* Deploy built API by installing WSGI complaint server on any of the cloud platforms

<h3>Further Learning:</h3>
* https://restfulapi.net/
* https://httpstatuses.com/
* https://flask-marshmallow.readthedocs.io/en/latest/