First REST API with Flask
Build your first REST API with Flask — Python web development.
Focus: build your first rest api with flask
You've mastered Python syntax, data structures, and even some command-line tools. But when you need to share data between a frontend, a mobile app, or another server, you need an API — and building your first REST API with Flask is the fastest way to get there. Without an API, your Python skills stay locked inside a single script, invisible to the world. In this lesson, you'll create a working REST API from scratch, understand the core concepts behind HTTP methods and endpoints, and learn to debug the most common pitfalls — so you can ship your first backend service with confidence.
The Problem This Lesson Solves
Imagine you've built a brilliant Python script that analyzes text, tracks expenses, or manages a to-do list. It works perfectly when you run it on your machine. But how does your React frontend call it? How does your mobile app fetch that data? The answer is an API — a set of rules that let different software systems talk to each other.
REST (Representational State Transfer) is the most common architectural style for web APIs. It uses standard HTTP methods like GET, POST, PUT, and DELETE to interact with resources. If you don't understand REST, you risk building an endpoint that works on your machine but breaks in production, or worse, an API that's insecure and slow. This lesson strips away the confusion and shows you exactly how to build a simple, robust REST API with Flask.
Core Concept / Mental Model
Think of a REST API as a waiter in a restaurant. Your client (the diner) makes a request: "I'd like the menu of the day" (GET), "Please add a new dish to the menu" (POST), "Update this dish's price" (PUT), or "Remove this dish" (DELETE). The waiter (your API) takes that request, talks to the kitchen (your database or data structure), and brings back a response — usually in JSON format.
Here are the key terms you'll need:
- Resource: A thing your API manages, like
users,posts, ortasks. - Endpoint: A URL path that maps to a resource, e.g.,
/api/tasks. - HTTP Method: The action you want to perform — GET (read), POST (create), PUT (update), DELETE (delete).
- Status Code: The result of the request — 200 for success, 404 for not found, 201 for created, 400 for a bad request.
- JSON: The data format used for input and output — human-readable and language-agnostic.
Pro tip: You don't need a database to start. You can use a simple Python list or dictionary as your "database" to learn the mechanics. Swap it later for SQLite or PostgreSQL when you're ready.
How It Works Step by Step
Building a REST API with Flask follows a predictable pattern. Here's the logical flow:
- Set up the environment: Create a virtual environment and install Flask.
- Initialize the app: Create a Flask application instance.
- Define routes and methods: Use decorators like
@app.route('/api/tasks', methods=['GET'])to expose endpoints. - Process requests: Read data from
request.jsonor URL parameters. - Return JSON responses: Use
jsonify()to convert Python data to JSON with proper status codes. - Test and debug: Use a browser, curl, or Postman to verify each endpoint.
Let's apply this in a concrete walkthrough.
Hands-On Walkthrough
Step 1: Set Up Your Project
Create a new directory and a virtual environment:
mkdir flask-api-tutorial
cd flask-api-tutorial
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install flask
Step 2: Write a Minimal Flask App
Create a file named app.py with the following code:
from flask import Flask, jsonify, request
app = Flask(__name__)
# In-memory "database"
tasks = []
next_id = 1
@app.route('/')
def home():
return "Welcome to the Task API!"
@app.route('/api/tasks', methods=['GET'])
def get_tasks():
"""Return all tasks as JSON."""
return jsonify(tasks)
@app.route('/api/tasks', methods=['POST'])
def create_task():
"""Create a new task from JSON body."""
global next_id
data = request.get_json()
if not data or not data.get('title'):
return jsonify({'error': 'Title is required'}), 400
task = {
'id': next_id,
'title': data['title'],
'done': data.get('done', False)
}
tasks.append(task)
next_id += 1
return jsonify(task), 201
if __name__ == '__main__':
app.run(debug=True)
Run the app:
python app.py
You'll see output like Running on http://127.0.0.1:5000. Open that URL in a browser — you'll see the welcome message.
Step 3: Test Your API with curl
In a second terminal, test the endpoints:
# GET all tasks (should be empty array)
curl http://127.0.0.1:5000/api/tasks
# Add a task
curl -X POST http://127.0.0.1:5000/api/tasks -H "Content-Type: application/json" -d '{"title": "Learn Flask"}'
# GET again
curl http://127.0.0.1:5000/api/tasks
Expected output: The first GET returns []. The POST returns the created task with status 201. The second GET shows:
[{"done": false, "id": 1, "title": "Learn Flask"}]
Step 4: Add GET for a Single Task and DELETE
Extend app.py with these routes:
@app.route('/api/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
"""Return a specific task by ID."""
task = next((t for t in tasks if t['id'] == task_id), None)
if task is None:
return jsonify({'error': 'Task not found'}), 404
return jsonify(task)
@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
"""Delete a task by ID."""
global tasks
task = next((t for t in tasks if t['id'] == task_id), None)
if task is None:
return jsonify({'error': 'Task not found'}), 404
tasks = [t for t in tasks if t['id'] != task_id]
return jsonify({'message': 'Task deleted'})
Restart the server and test:
curl http://127.0.0.1:5000/api/tasks/1
curl -X DELETE http://127.0.0.1:5000/api/tasks/1
Pro tip: Use
debug=Trueduring development so the server auto-reloads after code changes. Never enable debug mode in production — it can expose sensitive data and execute arbitrary code.
Compare Options / When to Choose What
Flask is lightweight and flexible, but it's not the only web framework. How does it compare to others?
| Framework | Strengths | Weaknesses | Best For |
|---|---|---|---|
| Flask | Minimal, easy to learn, highly customizable | Requires manual setup for ORM, auth, admin | Small to medium APIs, microservices, learning |
| FastAPI | Async support, automatic OpenAPI docs, type hints | Newer ecosystem, less "batteries included" | High-performance APIs, async workloads |
| Django REST Framework (DRF) | Full-featured, includes ORM, admin, auth | Steep learning curve, heavier | Large production apps, rapid prototyping with admin |
When to use Flask: - You want total control over each part of your API. - You're building a microservice or a small to medium project. - You're learning web development and want to understand HTTP fundamentals.
When to avoid Flask: - You need a full framework with built-in admin panel and ORM from day one (choose Django). - You're building a highly concurrent API with heavy I/O and want async without extra dependencies (choose FastAPI).
As an alternative, you can add the Flask-RESTful extension for more structured API building, or use Flask-Smorest for OpenAPI documentation — but plain Flask is perfectly fine to start.
Troubleshooting & Edge Cases
Common Mistakes and Fixes
-
Forgot to call
jsonify()— Returning a Python dict directly from a route works, but in older Flask versions it's not automatic. Always usejsonify()for consistency. -
Using
debug=Truein production — This can lead to a security vulnerability. Setdebug=Falseor use environment variables for configuration. -
Not validating input — A POST request might have missing fields or wrong types. Always check for required fields and use
try/exceptto handle malformed JSON. -
Mutable global state — Using a global list for tasks works, but it resets on restart and isn't thread-safe. For production, use a database.
Edge Cases
- Invalid JSON body:
request.get_json()returnsNoneif the content-type isn't JSON. Handle it:python data = request.get_json(silent=True) if data is None: return jsonify({'error': 'Invalid JSON'}), 400 - ID not found: Always return a proper 404 status code, not an empty result.
- Type coercion:
data.get('done', False)might receive a non-boolean value. Consider validating types.
What You Learned & What's Next
You've built your first REST API with Flask. You can now:
- Explain the core concept of REST and why APIs matter.
- Set up a Flask app and define routes for GET and POST.
- Return JSON responses with
jsonify()and appropriate status codes. - Test your endpoints with
curland troubleshoot common errors.
Your next step in the Python web development track is to connect this API to a real database (likely PostgreSQL via SQLAlchemy). That will let you persist data across server restarts and add authentication and more robust error handling. You're on track to building production-ready backends!
Practice recap
Check that your code handles a missing title field in a POST request by returning a 400 error. Then add a PUT endpoint to update a task's done status and test it with curl. Finally, try adding a priority field (low, medium, high) to a task and validate that it's one of those values.
Common mistakes
- Forgetting to call
jsonify()and returning raw dicts or strings — always usejsonify()for JSON responses. - Exposing
debug=Truein production, which can leak source code or run arbitrary code. - Not validating input — a missing
titlefield causes a crash instead of a 400 error. - Using a mutable global list as a database — data resets on restart and isn't thread-safe; use a real database for anything beyond learning.
Variations
- Use Flask-RESTful to structure routes and requests with classes, reducing boilerplate.
- Prefer FastAPI if you need async endpoints and automatic OpenAPI documentation with type hints.
- Add SQLAlchemy to persist tasks in a real database instead of an in-memory list.
Real-world use cases
- A weather app calls a Flask REST API to fetch forecast data for a location, with GET and POST endpoints for user preferences.
- A to-do list web app uses Flask REST endpoints to create, read, and delete tasks, with a React frontend that consumes the API.
- A microservice in a larger system exposes inventory data via a Flask REST API, allowing other services to query stock levels and update them.
Key takeaways
- REST APIs use HTTP methods (GET, POST, PUT, DELETE) to interact with resources via JSON.
- Flask routes are defined with
@app.route('/path', methods=['...']), and you return JSON usingjsonify(). - Always validate incoming data and return appropriate status codes (201, 400, 404) for robust APIs.
- In-memory data structures are fine for learning, but production APIs need a database for persistence.
- Use
curlor Postman to test your endpoints during development for quick feedback. - Never enable debug mode in production — it's a security risk.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.