Flask RESTful API Best Practices
Best practices for how to implement an API using Flask

First of all, let’s understand some basics concepts before putting our hands-on API coding. In this guide, the following topics are covered: APIs vs WebServices, HTTP methods vs CRUD, and endpoints best practices.
APIs
Application Programming Interface (API) is simply an interface, used to help two different systems to interact with one another. There are two key ideas behind this:
- Don’t expose your application implementation;
- Easier to access your application’s data.
Previously, I made a tutorial on How to implement a CRUD Application using Flask, it was a WebService also implementing CRUD methods. As follows, we can notice the main differences between them:
WebService
- The Server is responsible for handling the entire page requested by the Client. In brief, this stack can be seen as an internal system.
- Supports just a single Client. As the Server renders HTML, CSS, and JS, an Android or iOS Client cannot operate it.
APIs
- The Server is responsible for handling the information required by the Client, and returns it in JSON format, for instance.
- Supports n-clients. Clients can make requests to your API since there is provided a standardized way to access it.
REST
Representational State Transfer (REST) is an architectural style defined by key principles. In summary, they are:
- Uniform Interface
There must be a standardized way of accessing your API routes. - Stateless
Every client request has nothing to do with previous or subsequent requests. - Client-Server
There must be a client a server and they act individually. - Cacheable & Layered System
Responsible for networking efficiency and consistent behavior regardless of intermediaries.
This style was first introduced by Roy Fielding in his Ph.D. thesis at the University of California. If you want to check it in more depth, download it.
HTTP Requests
APIs primarily transmit data to the client via HTTP. This protocol has its own methods and elements:
- GET: ONLY retrieves information for the requested resource of the given URL.
- POST: Send data to the server to create a new resource.
- PUT: Replaces all of the representation of the target resource with the requested data.
- PATCH: Partially modifies the representation of the target resource with the requested data.
- DELETE: Removes all of the representations of the resource specified by the URL.
- OPTIONS: Sends the communication options for the requested resource.
Another prominent concept in the API industry is the acronym CRUD, which refers to four basic operations performed on database servers.
- Create
When the client sends a POST request to the API, the create function is responsible to handle it. - Read
When the client sends a GET request to the API, the read function is responsible to handle it. - Update
When the client sends a PUT or PATCH request to the API, the update function is responsible to handle it. - Delete
When the client sends a DELETE request to the API, the delete** function is responsible to handle it.
Bear in mind that, REST APIs are not limited to CRUD functions.
“As long as the method is being used according to its own definition, REST doesn’t have much to say about it.” — Roy Fielding
Endpoints Conventions
There are some principles in how to define your endpoints. In other words:
- Should be intuitive
- Organized by resource: Use nouns instead of verbs
- Consistent scheme: Specify plural nouns for collections
- Not too lengthy:
collection/item/collection
Let’s see some examples.
Get all users: /collection
Get a specific user: /collection/item
- good:
GET https://example.com/users/1
- bad:
GET https://example.com/get_user_one
Edit a specific user: /collection/item
- good:
PATCH https://example.com/users/1
- bad:
PATCH https://example.com/user/1/update
Edit some information (all notes) of user 1: /collection/item/collection
- good:
PATCH https://example.com/users/1/notes
- bad:
PATCH https://example.com/user/1/notes/edit
Edit specific note of user 1: /collection/item
- good:
PATCH https://example.com/notes/{id_note}
- bad:
PATCH https://example.com/user/1/notes/{id_note}
As you can see, following the pattern /collection/item/collection
could not always be the best decision.
In PATCH https://example.com/user/1/notes/{id_note}
, it can not simply be a bad resource if you want it to contain sub-collection resources. For example, to differentiate user notes from companies' notes.
However, bear in mind that /collection/item/subcolletion/item
becomes lengthy, and putting more and more subcollections turns it into a long-term problem.
In brief, the most important thing in resource naming is consistency, and once decided the pattern it needs to be pursued in the entire application.
The project structure
In this guide, the following structure is used:
project/
|
├── app/
| └── __init__.py
|
├── migrations/
| └── ...
|
└── models.py
Do not forget to run these commands:
export FLASK_APP='app'
export FLASK_ENV='development'or$env:FLASK_APP='app'
$env:FLASK_ENV='development'
Hands-on
Let’s write our CRUD verbs and actions with our routes. In the the __init__.py
file:
index:
@app.route('/users')
def index():
users = User.query.order_by(User.id).all()
if len(users) == 0:
abort(404) return jsonify({
'success': True,
'users': users
})
store:
@app.route('/users', methods=['POST'])
def store():
body = request.get_json()
name = body.get('name', None)
age = body.get('age', None) try:
user = User(name=name, age=age)
user.create() return jsonify({
'success': True,
'created': user.format()
})
except:
abort(422)
show:
@app.route('/users/<int:user_id>')
def show(user_id):
user = User.query.filter(User.id == user_id).one_or_none()
if user is None:
abort(404)
else:
return jsonify({
'success': True,
'user': user.format()
})
update:
@app.route('/users/<int:user_id>', methods=['PATCH'])
def update(user_id):
body = request.get_json() try:
user = User.query.filter(User.id == user_id).one_or_none()
if user is None:
abort(404) if 'name' in body:
user.name = body.get('name') user.update() return jsonify({
'success': True,
'updated': user.format()
})
except:
abort(422)
destroy:
@app.route('/users/<int:user_id>', methods=['DELETE'])
def destroy(user_id):
try:
user = User.query.filter(User.id == user_id).one_or_none()
if user is None:
abort(404) user.delete() return jsonify({
'success': True,
'deleted': user.format()
})
except:
abort(422)
Maybe you can notice that some names are different from our CRUD understanding. CRUD is typically used within models and index, store, show, update, destroy
in Controllers.
Now, having the controllers and routes done, it is time to implement our model.py
After that, we can finally run the commands:
flask db init
flask db migrate
flask db upgrade
flask run
And your REST API is done! You can check if your project is correctly running at http://localhost:5000/
Conclusion
I hope I’ve shown you how easy it can be to implement a RESTful API using Flask. Thanks for reading!
More content at plainenglish.io