In this blog post, I will show you how to create a simple RESTful API using APIFlask. Finally we will check with pytest if everything is working as expected. This API leverages JSON Web Tokens (JWT) for authentication and Bearer tokens for security. The entire code is written in Python and utilizes the Flask web framework.
Installing Required Libraries#
Before we start, ensure you have the necessary libraries installed. Use the following pip commands:
pip install apiflask flask-jwt-extended pytest
Setting Up the API#
Let’s start by importing the necessary modules and setting up the API:
from apiflask import APIFlask, Schema, HTTPTokenAuth
from apiflask.fields import Integer, String
from flask import jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
Now, create an instance of APIFlask
and configure the JWT Manager:
app = APIFlask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret-key' # Change this to a random secret key
jwt = JWTManager(app)
auth = HTTPTokenAuth(scheme='Bearer')
Defining Schemas#
Next, let’s define the schemas for our data structures:
items = []
class UserSchema(Schema):
username = String()
password = String()
users = {
'testuser': 'testpassword'
}
Creating Endpoints#
Login Endpoint#
The login endpoint allows a user to log in with a username and password and receive a JWT:
@app.post('/login')
@app.input(UserSchema)
def login(json_data):
username = json_data.get('username')
password = json_data.get('password')
if username in users and users[username] == password:
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token), 200
return jsonify({"msg": "Bad username or password"}), 401
Verifying the Token#
We use HTTPTokenAuth
to verify the token:
@auth.verify_token
def verify_token(token):
try:
user_identity = get_jwt_identity()
return user_identity
except:
return None
CRUD Endpoints for Items#
Now, let’s define the endpoints for creating, retrieving, updating, and deleting items. All these endpoints are protected by JWT and token authentication:
@app.get('/items')
@jwt_required()
@app.auth_required(auth)
def get_items():
return jsonify(items), 200
@app.post('/items')
@jwt_required()
@app.auth_required(auth)
def create_item():
new_item = {'id': len(items) + 1, 'name': 'Item ' + str(len(items) + 1)}
items.append(new_item)
return jsonify(new_item), 201
@app.put('/items/<int:item_id>')
@jwt_required()
@app.auth_required(auth)
def update_item(item_id):
for item in items:
if item['id'] == item_id:
item['name'] = 'Updated Item ' + str(item_id)
return jsonify(item), 200
return jsonify({'message': 'Item not found'}), 404
@app.delete('/items/<int:item_id>')
@jwt_required()
@app.auth_required(auth)
def delete_item(item_id):
global items
items = [item for item in items if item['id'] != item_id]
return '', 204
Starting the Application#
Finally, we start the application:
if __name__ == '__main__':
app.run()
Adding Tests with Pytest#
It’s important to ensure our API is well-tested. Here, we use pytest
to test various aspects of our API.
Create a file test_app.py
with the following content:
import pytest
from app2 import app
@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_login(client):
response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
assert response.status_code == 200
assert 'access_token' in response.json
response = client.post('/login', json={'username': 'testuser', 'password': 'wrongpassword'})
assert response.status_code == 401
def test_get_items(client):
login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
token = login_response.json['access_token']
response = client.get('/items', headers={'Authorization': f'Bearer {token}'})
assert response.status_code == 200
assert response.json == []
def test_create_item(client):
login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
token = login_response.json['access_token']
response = client.post('/items', headers={'Authorization': f'Bearer {token}'})
assert response.status_code == 201
assert 'id' in response.json
assert 'name' in response.json
def test_update_item(client):
login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
token = login_response.json['access_token']
response = client.put('/items/1', headers={'Authorization': f'Bearer {token}'})
assert response.status_code == 200
assert response.json['name'] == 'Updated Item 1'
response = client.put('/items/2', headers={'Authorization': f'Bearer {token}'})
assert response.status_code == 404
def test_delete_item(client):
login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
token = login_response.json['access_token']
response = client.delete('/items/1', headers={'Authorization': f'Bearer {token}'})
assert response.status_code == 204
response = client.get('/items', headers={'Authorization': f'Bearer {token}'})
assert response.json == []
Running the Tests#
Run the tests with the following command:
pytest
Results ….
====================================== test session starts =======================================
platform darwin -- Python 3.12.3, pytest-8.2.2, pluggy-1.5.0
rootdir: /Users/netdevops/_DEV/log
collected 5 items
test_app.py ..... [100%]
======================================= 5 passed in 0.13s ========================================
Summary#
In this post, we created a simple RESTful API using APIFlask, leveraging JWT for authentication to secure our endpoints. We defined endpoints for logging in, creating, retrieving, updating, and deleting items, and tested our API with pytest
. This example serves as a foundation that you can expand and tailor to your specific requirements.
Happy coding!