In today’s micro‑service world, a lightweight REST API is often the first building block of a larger application. Python’s Flask framework makes it surprisingly easy to spin up an API that can handle JSON payloads, route requests, and integrate with databases—all with just a few lines of code. This guide walks you through the entire process, from setting up a virtual environment to testing your endpoints with curl. By the end, you’ll have a functional API you can extend, deploy, and showcase in your portfolio.
What You'll Need
- Python 3.8 or newer installed on your machine
- Basic knowledge of Python syntax and HTTP concepts
- A terminal or command prompt with administrative rights
- Virtual environment tools (venv or virtualenv)
- Text editor or IDE (VS Code, PyCharm, etc.)
Step 1: Set Up a Clean Project Workspace
Start by creating a dedicated folder for your API. Open a terminal and run:
mkdir flask_api_demo && cd flask_api_demo
Next, initialise a Python virtual environment to keep dependencies isolated:
python -m venv venv
Activate the environment:
# On Windows
venvScriptsactivate
# On macOS/Linux
source venv/bin/activate
With the environment active, install Flask and a helper library called Flask‑RESTful, which simplifies route handling:
pip install Flask Flask-RESTful
Verify the installation by checking the package list:
pip list
Step 2: Create the Core Application File
Inside your project folder, create a file named app.py. This file will hold the Flask application instance and the API resources.
touch app.py
Open app.py in your editor and paste the following skeleton:
from flask import Flask, request, jsonify
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {"message": "Hello, World!"}, 200
api.add_resource(HelloWorld, "/api/hello")
if __name__ == "__main__":
app.run(debug=True)
This minimal app defines a single GET endpoint at /api/hello that returns a JSON greeting. The debug=True flag enables live reloading—handy while you develop.
Step 3: Add CRUD Endpoints for a Simple Resource
Let’s build a more realistic example: a tiny to‑do list API that supports Create, Read, Update, and Delete (CRUD) operations. First, decide on the data structure. For this tutorial we’ll keep items in an in‑memory Python list, which means the data resets when the server restarts—perfect for learning purposes.
Replace the previous HelloWorld class with the following code in app.py:
todos = []
class TodoList(Resource):
def get(self):
return jsonify(todos)
def post(self):
data = request.get_json(force=True)
if not data.get("task"):
return {"error": "'task' field is required"}, 400
todo = {"id": len(todos) + 1, "task": data["task"], "done": False}
todos.append(todo)
return todo, 201
class TodoItem(Resource):
def get(self, todo_id):
todo = next((t for t in todos if t["id"] == todo_id), None)
if not todo:
return {"error": "Todo not found"}, 404
return todo, 200
def put(self, todo_id):
todo = next((t for t in todos if t["id"] == todo_id), None)
if not todo:
return {"error": "Todo not found"}, 404
data = request.get_json(force=True)
todo["task"] = data.get("task", todo["task"])
todo["done"] = data.get("done", todo["done"])
return todo, 200
def delete(self, todo_id):
global todos
todos = [t for t in todos if t["id"] != todo_id]
return {"message": "Deleted"}, 200
api.add_resource(TodoList, "/api/todos")
api.add_resource(TodoItem, "/api/todos/")
Explanation of each method:
- GET /api/todos – Returns the full list of to‑do items.
- POST /api/todos – Accepts JSON
{"task": "Buy milk"}and creates a new entry. - GET /api/todos/<id> – Retrieves a single item by its numeric ID.
- PUT /api/todos/<id> – Updates the
taskordonefields. - DELETE /api/todos/<id> – Removes the item from the list.
Save the file and restart the server (Ctrl‑C then python app.py). You should see Flask listening on http://127.0.0.1:5000.
Step 4: Test Your API with curl
Open a second terminal window and run a series of curl commands to verify each endpoint works as expected.
Create a new to‑do:
curl -X POST -H "Content-Type: application/json" -d '{"task": "Write documentation"}' http://127.0.0.1:5000/api/todos
Fetch the list:
curl http://127.0.0.1:5000/api/todos
Update the first item to mark it as done:
curl -X PUT -H "Content-Type: application/json" -d '{"done": true}' http://127.0.0.1:5000/api/todos/1
Delete the item:
curl -X DELETE http://127.0.0.1:5000/api/todos/1
If any command returns an error, double‑check the JSON payload and the route spelling. The responses should be valid JSON objects with appropriate HTTP status codes (200, 201, 400, 404, etc.).
Step 5: Add Basic Error Handling and Input Validation
Flask‑RESTful provides a convenient abort function, but for clarity we’ll implement simple checks ourselves. Update the post method in TodoList to use abort when the payload is malformed:
from flask_restful import abort
def abort_if_missing(field, data):
if field not in data:
abort(400, message=f"'{field}' is required")
class TodoList(Resource):
def post(self):
data = request.get_json(force=True)
abort_if_missing('task', data)
# rest of the method stays the same
Similarly, wrap the lookup logic in a helper function to avoid repetition:
def find_todo(todo_id):
todo = next((t for t in todos if t["id"] == todo_id), None)
if not todo:
abort(404, message="Todo not found")
return todo
Replace each direct next(...) call with find_todo(todo_id). This makes the code cleaner and guarantees consistent error messages.
Step 6: Prepare for Production – Configuration and WSGI Server
Running app.run(debug=True) is fine for development, but a production environment should use a robust WSGI server like gunicorn. First, install it inside your virtual environment:
pip install gunicorn
Then create a new file called wsgi.py that simply imports the Flask app:
from app import app
if __name__ == "__main__":
app.run()
Start the server with:
gunicorn --bind 0.0.0.0:8000 wsgi:app
Gunicorn will manage worker processes, handle concurrent requests, and provide better performance. Remember to set debug=False in app.py before deploying.
Common Mistakes to Avoid
1. Hard‑coding the port or host. Using 0.0.0.0 lets the app accept external traffic; 127.0.0.1 restricts it to localhost only.
2. Storing data in a global list. It works for demos but will lose data on restart. For real projects, switch to a database like SQLite or PostgreSQL.
3. Neglecting content‑type headers. Clients must send Content-Type: application/json for POST/PUT; otherwise request.get_json() returns None.
4. Returning plain Python dicts without jsonify. Flask will convert them, but jsonify also sets the correct MIME type.
5. Running the app with debug=True in production. It exposes the interactive debugger, which is a security risk.
Tips and Tricks
• Use flask‑script or Flask CLI to manage commands like database migrations.
• Leverage Flask’s @app.errorhandler decorator to centralise error responses.
• Enable CORS with the flask‑cors extension if your API will be called from browsers on different domains.
• Structure larger projects with blueprints: each resource lives in its own module, keeping app.py tidy.
• Write unit tests with pytest and Flask’s test client to catch regressions early.
Frequently Asked Questions
Do I need a database for a Flask API?
Not for a simple prototype. An in‑memory list suffices for learning, but production‑grade APIs should persist data in a relational (PostgreSQL, MySQL) or NoSQL (MongoDB) store.
Can I secure my API with authentication?
Yes. Flask‑HTTPAuth or Flask‑JWT‑Extended are popular extensions that let you add token‑based authentication, OAuth, or API key checks with minimal code.
How do I deploy the API to the cloud?
Package your code in a Docker container, push it to a registry, and run it on services like AWS Elastic Beanstalk, Google Cloud Run, or Azure App Service. The gunicorn command shown earlier works inside the container as the entrypoint.
Conclusion
Building a REST API with Flask is a straightforward yet powerful exercise that introduces you to core web concepts—routing, JSON handling, error management, and production deployment. By following the six steps above, you now have a functional API that you can expand with authentication, a real database, and automated testing. Keep iterating, explore Flask extensions, and soon you’ll be comfortable crafting full‑featured micro‑services for any modern application.
Photo by Raghav Bhasin on Unsplash




