Flask Swagger Integration
Introduction
When developing RESTful APIs with Flask, providing clear and interactive documentation is essential for other developers who will consume your API. Swagger (now known as OpenAPI) is a powerful tool for documenting and visualizing your API endpoints. In this tutorial, you'll learn how to integrate Swagger with your Flask application to create interactive, user-friendly API documentation.
Swagger allows you to:
- Document your API endpoints in a standardized format
- Test API endpoints directly from a web interface
- Generate client SDKs automatically
- Make your APIs more accessible to others
Prerequisites
Before getting started, make sure you have:
- Basic knowledge of Flask and RESTful API concepts
- Python 3.6 or later installed
- A simple Flask application to work with
Setting Up Flask-Swagger
The most common way to integrate Swagger with Flask is using the flask-swagger-ui
package, which provides a Flask blueprint for the Swagger UI and a method for generating Swagger specifications.
Step 1: Install Required Packages
First, let's install the necessary packages:
pip install flask flask-restful flask-swagger-ui apispec marshmallow
Step 2: Create a Basic Flask Application
Let's start with a simple Flask API:
from flask import Flask, jsonify, request
app = Flask(__name__)
# Sample data
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg"},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes"}
]
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify(books)
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book:
return jsonify(book)
return jsonify({"error": "Book not found"}), 404
@app.route('/api/books', methods=['POST'])
def add_book():
if not request.json or 'title' not in request.json:
return jsonify({"error": "Invalid data"}), 400
book = {
'id': books[-1]['id'] + 1 if books else 1,
'title': request.json['title'],
'author': request.json.get('author', '')
}
books.append(book)
return jsonify(book), 201
if __name__ == '__main__':
app.run(debug=True)
Integrating Swagger UI with Your Flask Application
Now, let's integrate Swagger UI with our Flask application.
Step 3: Create a Swagger Configuration
We'll create a function to generate the Swagger documentation:
from flask_swagger_ui import get_swaggerui_blueprint
# Define the Swagger blueprint
SWAGGER_URL = '/api/docs' # URL for exposing Swagger UI (without trailing '/')
API_URL = '/static/swagger.json' # Our API url (can be a local file or url)
# Call factory function to create our blueprint
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={ # Swagger UI config overrides
'app_name': "Flask Book API"
}
)
# Register blueprint at URL
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
Step 4: Create a Swagger Specification File
Now we need to create a Swagger specification file that describes our API. Create a static
folder in your project root and add a file called swagger.json
:
import os
from flask import Flask, jsonify, request, send_from_directory
from flask_swagger_ui import get_swaggerui_blueprint
app = Flask(__name__)
# Create a route for serving the swagger.json file
@app.route('/static/swagger.json')
def swagger_json():
with open('static/swagger.json', 'r') as f:
return jsonify(json.load(f))
Now, create a static
folder and add a file named swagger.json
with the following content:
{
"swagger": "2.0",
"info": {
"title": "Flask Book API",
"description": "API for managing books",
"version": "1.0.0"
},
"basePath": "/api",
"schemes": [
"http"
],
"paths": {
"/books": {
"get": {
"summary": "Returns all books",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "Successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Book"
}
}
}
}
},
"post": {
"summary": "Add a new book",
"produces": [
"application/json"
],
"parameters": [
{
"name": "book",
"in": "body",
"description": "Book object",
"required": true,
"schema": {
"$ref": "#/definitions/Book"
}
}
],
"responses": {
"201": {
"description": "Book created",
"schema": {
"$ref": "#/definitions/Book"
}
},
"400": {
"description": "Invalid input"
}
}
}
},
"/books/{bookId}": {
"get": {
"summary": "Find book by ID",
"produces": [
"application/json"
],
"parameters": [
{
"name": "bookId",
"in": "path",
"description": "ID of book to return",
"required": true,
"type": "integer"
}
],
"responses": {
"200": {
"description": "Successful operation",
"schema": {
"$ref": "#/definitions/Book"
}
},
"404": {
"description": "Book not found"
}
}
}
}
},
"definitions": {
"Book": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"title": {
"type": "string"
},
"author": {
"type": "string"
}
}
}
}
}
Step 5: Update the Main Application File
Now, let's put everything together in the main application file:
from flask import Flask, jsonify, request, send_from_directory
from flask_swagger_ui import get_swaggerui_blueprint
import json
import os
app = Flask(__name__)
# Sample data
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg"},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes"}
]
# Swagger configuration
SWAGGER_URL = '/api/docs' # URL for exposing Swagger UI
API_URL = '/static/swagger.json' # URL where our API spec is located
# Create Swagger UI blueprint
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={
'app_name': "Flask Book API"
}
)
# Register blueprint
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
# Create a route for serving the swagger.json file
@app.route('/static/swagger.json')
def serve_swagger_spec():
return send_from_directory('static', 'swagger.json')
# API routes
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify(books)
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book:
return jsonify(book)
return jsonify({"error": "Book not found"}), 404
@app.route('/api/books', methods=['POST'])
def add_book():
if not request.json or 'title' not in request.json:
return jsonify({"error": "Invalid data"}), 400
book = {
'id': books[-1]['id'] + 1 if books else 1,
'title': request.json['title'],
'author': request.json.get('author', '')
}
books.append(book)
return jsonify(book), 201
if __name__ == '__main__':
app.run(debug=True)
Automating Swagger Documentation with Flask-RESTful and ApiSpec
Manually creating swagger.json
can be tedious. Let's look at how to automate this process using flask-restful
and apispec
.
Step 6: Installing Additional Packages
pip install flask-restful apispec
Step 7: Creating a More Advanced Example with Automated Documentation
from flask import Flask, jsonify, request
from flask_restful import Api, Resource
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from marshmallow import Schema, fields
from flask_swagger_ui import get_swaggerui_blueprint
app = Flask(__name__)
api = Api(app)
# Create an APISpec
spec = APISpec(
title="Flask Book API",
version="1.0.0",
openapi_version="3.0.2",
plugins=[FlaskPlugin(), MarshmallowPlugin()]
)
# Define schemas
class BookSchema(Schema):
id = fields.Int(dump_only=True)
title = fields.Str(required=True)
author = fields.Str(required=True)
# Register schemas with spec
spec.components.schema("Book", schema=BookSchema)
# Sample data
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg"},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes"}
]
# Resources
class BookResource(Resource):
def get(self, book_id):
"""Get a book by ID
---
parameters:
- in: path
name: book_id
required: true
schema:
type: integer
responses:
200:
description: Return a book
content:
application/json:
schema: BookSchema
404:
description: Book not found
"""
book = next((book for book in books if book['id'] == book_id), None)
if book:
return book
return {"error": "Book not found"}, 404
class BookListResource(Resource):
def get(self):
"""Get all books
---
responses:
200:
description: Return a list of books
content:
application/json:
schema:
type: array
items: BookSchema
"""
return books
def post(self):
"""Add a new book
---
requestBody:
content:
application/json:
schema: BookSchema
responses:
201:
description: Book created
content:
application/json:
schema: BookSchema
400:
description: Invalid input
"""
if not request.json or 'title' not in request.json:
return {"error": "Invalid data"}, 400
book = {
'id': books[-1]['id'] + 1 if books else 1,
'title': request.json['title'],
'author': request.json.get('author', '')
}
books.append(book)
return book, 201
# Register resources
api.add_resource(BookListResource, '/api/books')
api.add_resource(BookResource, '/api/books/<int:book_id>')
# Register paths with spec
with app.test_request_context():
spec.path(view=BookListResource, app=app)
spec.path(view=BookResource, app=app)
# Serve OpenAPI specification
@app.route('/api/swagger.json')
def create_swagger_spec():
return jsonify(spec.to_dict())
# Configure Swagger UI
SWAGGER_URL = '/api/docs'
API_URL = '/api/swagger.json'
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={
'app_name': "Flask Book API"
}
)
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
if __name__ == '__main__':
app.run(debug=True)
Testing the Swagger UI
- Run your Flask application:
python app.py
- Open a web browser and navigate to
http://127.0.0.1:5000/api/docs
You should see the Swagger UI interface displaying your API documentation. From here, you can:
- View all available endpoints
- Understand the expected parameters and responses
- Test endpoints directly through the interface
- See example responses
Best Practices for Swagger Documentation
- Be Comprehensive: Document all endpoints, parameters, and possible responses
- Include Examples: Provide example requests and responses
- Keep It Updated: Always update your documentation when you change your API
- Use Descriptive Names: Use clear, descriptive names for endpoints and models
- Group Related Endpoints: Organize your API documentation by grouping related endpoints
- Document Error Responses: Include all possible error responses for each endpoint
Real-World Example: Building a Complete Book Management API
Let's expand our example to include more operations like updating and deleting books:
from flask import Flask, jsonify, request
from flask_restful import Api, Resource
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from marshmallow import Schema, fields
from flask_swagger_ui import get_swaggerui_blueprint
app = Flask(__name__)
api = Api(app)
# Create an APISpec
spec = APISpec(
title="Book Management API",
version="1.0.0",
openapi_version="3.0.2",
plugins=[FlaskPlugin(), MarshmallowPlugin()]
)
# Define schemas
class BookSchema(Schema):
id = fields.Int(dump_only=True)
title = fields.Str(required=True, description="Book title")
author = fields.Str(required=True, description="Author name")
published_year = fields.Int(description="Year the book was published")
genre = fields.Str(description="Book genre")
# Register schemas with spec
spec.components.schema("Book", schema=BookSchema)
# Sample data
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg", "published_year": 2018, "genre": "Programming"},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes", "published_year": 2019, "genre": "Programming"}
]
# Resources
class BookResource(Resource):
def get(self, book_id):
"""Get a book by ID
---
parameters:
- in: path
name: book_id
required: true
schema:
type: integer
responses:
200:
description: Return a book
content:
application/json:
schema: BookSchema
404:
description: Book not found
"""
book = next((book for book in books if book['id'] == book_id), None)
if book:
return book
return {"error": "Book not found"}, 404
def put(self, book_id):
"""Update a book
---
parameters:
- in: path
name: book_id
required: true
schema:
type: integer
requestBody:
content:
application/json:
schema: BookSchema
responses:
200:
description: Book updated
content:
application/json:
schema: BookSchema
404:
description: Book not found
"""
book = next((book for book in books if book['id'] == book_id), None)
if not book:
return {"error": "Book not found"}, 404
if not request.json:
return {"error": "Invalid data"}, 400
book.update({
'title': request.json.get('title', book['title']),
'author': request.json.get('author', book['author']),
'published_year': request.json.get('published_year', book.get('published_year')),
'genre': request.json.get('genre', book.get('genre'))
})
return book
def delete(self, book_id):
"""Delete a book
---
parameters:
- in: path
name: book_id
required: true
schema:
type: integer
responses:
204:
description: Book deleted
404:
description: Book not found
"""
global books
book = next((book for book in books if book['id'] == book_id), None)
if not book:
return {"error": "Book not found"}, 404
books = [b for b in books if b['id'] != book_id]
return "", 204
class BookListResource(Resource):
def get(self):
"""Get all books
---
parameters:
- in: query
name: genre
schema:
type: string
description: Filter books by genre
responses:
200:
description: Return a list of books
content:
application/json:
schema:
type: array
items: BookSchema
"""
genre = request.args.get('genre')
if genre:
filtered_books = [book for book in books if book.get('genre') == genre]
return filtered_books
return books
def post(self):
"""Add a new book
---
requestBody:
content:
application/json:
schema: BookSchema
responses:
201:
description: Book created
content:
application/json:
schema: BookSchema
400:
description: Invalid input
"""
if not request.json or not request.json.get('title') or not request.json.get('author'):
return {"error": "Title and author are required"}, 400
book = {
'id': max(b['id'] for b in books) + 1 if books else 1,
'title': request.json['title'],
'author': request.json['author'],
'published_year': request.json.get('published_year'),
'genre': request.json.get('genre')
}
books.append(book)
return book, 201
# Register resources
api.add_resource(BookListResource, '/api/books')
api.add_resource(BookResource, '/api/books/<int:book_id>')
# Register paths with spec
with app.test_request_context():
spec.path(view=BookListResource, app=app)
spec.path(view=BookResource, app=app)
# Serve OpenAPI specification
@app.route('/api/swagger.json')
def create_swagger_spec():
return jsonify(spec.to_dict())
# Configure Swagger UI
SWAGGER_URL = '/api/docs'
API_URL = '/api/swagger.json'
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={
'app_name': "Book Management API"
}
)
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
if __name__ == '__main__':
app.run(debug=True)
Summary
In this tutorial, you've learned how to:
- Integrate Swagger UI with your Flask application
- Document your API manually using a Swagger JSON file
- Automate API documentation using
apispec
and docstrings - Build a complete RESTful API with comprehensive documentation
Swagger integration significantly improves the developer experience for those consuming your API. It provides clear, interactive documentation that allows users to understand and test your API directly from their browser.
Additional Resources
Exercises
- Add authentication to your API and document it in the Swagger specification
- Create a more complex model with relationships and document it
- Add query parameters for filtering and sorting to your GET endpoints
- Implement validation for request inputs using Marshmallow schemas
- Create a front-end application that consumes your API using the Swagger-generated client
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)