Building REST APIs

Back

Loading concept...

πŸ• REST APIs in Flask: Building Your Digital Pizza Delivery Service

Imagine you’re running a pizza shop. Customers can’t just walk into your kitchen and make their own pizza. Instead, they tell the cashier what they want, the cashier writes it down, sends it to the kitchen, and then brings back the pizza. REST APIs work exactly like this!

Your Flask app is the pizza shop. The API is the cashierβ€”the friendly middleman between hungry customers (other apps, websites, phones) and your delicious data kitchen.


🎯 What is a RESTful API?

REST stands for REpresentational State Transfer. Fancy words, simple idea!

Think of it like a library system:

  • You want a book? Tell the librarian the title β†’ They GET it for you
  • Want to donate a book? Give it to them β†’ They POST it to the shelf
  • Book has wrong info? Tell them β†’ They PUT the corrected version
  • Remove a book? Ask nicely β†’ They DELETE it
graph TD A["πŸ“± Client App"] -->|Request| B["πŸ• REST API"] B -->|Response| A B -->|Read/Write| C["πŸ’Ύ Database"]

The 4 Magic Words (HTTP Methods)

Method What it does Pizza Example
GET Fetch data β€œShow me the menu!”
POST Create new data β€œI want to order a new pizza!”
PUT Update existing data β€œChange my order to extra cheese!”
DELETE Remove data β€œCancel my order!”

πŸ›£οΈ API Endpoints Design: Creating Your Menu Board

An endpoint is like an address where customers go to get what they want. Good addresses are easy to understand and predict.

🎨 The Art of Good Endpoint Design

Bad Design (Confusing!)

/getAllPizzas
/createNewPizza
/updatePizzaById

Good Design (Clear!)

GET    /pizzas        β†’ List all pizzas
POST   /pizzas        β†’ Create a pizza
GET    /pizzas/1      β†’ Get pizza #1
PUT    /pizzas/1      β†’ Update pizza #1
DELETE /pizzas/1      β†’ Delete pizza #1

Flask Example: Your First Endpoints

from flask import Flask, jsonify

app = Flask(__name__)

pizzas = [
    {"id": 1, "name": "Margherita"},
    {"id": 2, "name": "Pepperoni"}
]

# GET all pizzas
@app.route('/pizzas', methods=['GET'])
def get_pizzas():
    return jsonify(pizzas)

# GET one pizza
@app.route('/pizzas/<int:id>',
           methods=['GET'])
def get_pizza(id):
    pizza = next(
        (p for p in pizzas if p['id'] == id),
        None
    )
    return jsonify(pizza)

🌟 Golden Rules for Endpoints

  1. Use nouns, not verbs: /users not /getUsers
  2. Plural names: /pizzas not /pizza
  3. Lowercase: /orders not /Orders
  4. Hyphens for spaces: /order-items not /orderItems

πŸ“Š HTTP Status Codes: The Emoji Language of APIs

When you text a friend, they reply with πŸ‘ or 😒. APIs do the same with status codes!

graph TD A["Status Codes"] --> B["2xx βœ… Success"] A --> C["4xx ❌ Your Fault"] A --> D["5xx πŸ’₯ Our Fault"] B --> E["200 OK"] B --> F["201 Created"] C --> G["400 Bad Request"] C --> H["404 Not Found"] D --> I["500 Server Error"]

The Most Important Codes

Code Meaning When to Use
200 OK! Everything worked perfectly
201 Created! New thing was made
400 Bad Request You sent garbage data
404 Not Found That thing doesn’t exist
500 Server Error Oops, we broke something

Flask Example: Returning Status Codes

from flask import Flask, jsonify

@app.route('/pizzas', methods=['POST'])
def create_pizza():
    # Created successfully!
    return jsonify({
        "message": "Pizza created!"
    }), 201  # ← Status code!

@app.route('/pizzas/<int:id>')
def get_pizza(id):
    pizza = find_pizza(id)
    if not pizza:
        return jsonify({
            "error": "Pizza not found"
        }), 404  # ← Not found!
    return jsonify(pizza), 200

πŸ“₯ JSON Request Handling: Understanding Customer Orders

When a customer orders a pizza, they fill out a form. In APIs, that form is JSON (JavaScript Object Notation).

What JSON Looks Like

{
  "name": "Supreme Pizza",
  "size": "large",
  "toppings": ["pepperoni", "mushrooms"]
}

It’s like a recipe card that both humans and computers can read!

Flask Example: Reading JSON Requests

from flask import Flask, request, jsonify

@app.route('/pizzas', methods=['POST'])
def create_pizza():
    # Get the JSON data
    data = request.get_json()

    # Read the values
    name = data.get('name')
    size = data.get('size')
    toppings = data.get('toppings', [])

    # Create the pizza
    new_pizza = {
        "id": len(pizzas) + 1,
        "name": name,
        "size": size,
        "toppings": toppings
    }
    pizzas.append(new_pizza)

    return jsonify(new_pizza), 201

πŸ”‘ Key Functions

Function What it does
request.get_json() Gets all JSON data
data.get('key') Gets one value safely
data.get('key', default) Gets value or default

πŸ“€ JSON Response Formatting: Sending Back the Receipt

After making a pizza, you give the customer a receipt. APIs send back JSON responses.

Flask Example: Beautiful Responses

from flask import jsonify

@app.route('/pizzas/<int:id>')
def get_pizza(id):
    pizza = {
        "id": 1,
        "name": "Margherita",
        "price": 12.99,
        "ready": True
    }
    return jsonify(pizza)

Output:

{
  "id": 1,
  "name": "Margherita",
  "price": 12.99,
  "ready": true
}

🎁 Response Best Practices

Include helpful metadata:

@app.route('/pizzas')
def get_pizzas():
    return jsonify({
        "data": pizzas,
        "count": len(pizzas),
        "success": True
    })

Wrap single items too:

@app.route('/pizzas/<int:id>')
def get_pizza(id):
    return jsonify({
        "data": pizza,
        "success": True
    })

⚠️ API Error Responses: When Things Go Wrong

Even the best pizza shop runs out of cheese sometimes. Good APIs explain problems clearly!

Flask Example: Helpful Error Messages

@app.route('/pizzas/<int:id>')
def get_pizza(id):
    pizza = find_pizza(id)

    if not pizza:
        return jsonify({
            "error": "Pizza not found",
            "message": f"No pizza with id {id}",
            "code": 404
        }), 404

    return jsonify(pizza)

🌟 Error Response Structure

{
  "error": "Short error name",
  "message": "Human-readable explanation",
  "code": 404,
  "details": {}
}

Global Error Handler

@app.errorhandler(404)
def not_found(error):
    return jsonify({
        "error": "Not Found",
        "message": "Resource doesn't exist"
    }), 404

@app.errorhandler(500)
def server_error(error):
    return jsonify({
        "error": "Server Error",
        "message": "Something went wrong"
    }), 500

βœ… Request Validation: Checking the Order Form

Before making a pizza, you check if the order makes sense. Validation ensures incoming data is correct.

Simple Validation Example

@app.route('/pizzas', methods=['POST'])
def create_pizza():
    data = request.get_json()

    # Check if data exists
    if not data:
        return jsonify({
            "error": "No data provided"
        }), 400

    # Check required fields
    if 'name' not in data:
        return jsonify({
            "error": "Name is required"
        }), 400

    # Check data types
    if not isinstance(data['name'], str):
        return jsonify({
            "error": "Name must be text"
        }), 400

    # All good! Create pizza
    # ...

🎯 Validation Checklist

graph TD A["Request Arrives"] --> B{Data exists?} B -->|No| C["❌ 400 Error"] B -->|Yes| D{Required fields?} D -->|Missing| C D -->|Present| E{Correct types?} E -->|Wrong| C E -->|Right| F["βœ… Process Request"]

Pro Tip: Create a Validation Helper

def validate_pizza(data):
    errors = []

    if not data.get('name'):
        errors.append("Name required")

    if not data.get('size'):
        errors.append("Size required")

    valid_sizes = ['small', 'medium', 'large']
    if data.get('size') not in valid_sizes:
        errors.append("Invalid size")

    return errors

@app.route('/pizzas', methods=['POST'])
def create_pizza():
    data = request.get_json()
    errors = validate_pizza(data)

    if errors:
        return jsonify({
            "errors": errors
        }), 400

    # Create pizza...

πŸ—οΈ Class-Based Views: Organizing Your Kitchen

Imagine your pizza shop grows. Instead of one person doing everything, you have specialized stations. Class-based views organize your code the same way!

The Old Way (Function Views)

@app.route('/pizzas', methods=['GET'])
def get_pizzas():
    return jsonify(pizzas)

@app.route('/pizzas', methods=['POST'])
def create_pizza():
    # create logic...

@app.route('/pizzas/<int:id>', methods=['GET'])
def get_pizza(id):
    # get one logic...

The New Way (Class-Based Views)

from flask.views import MethodView

class PizzaAPI(MethodView):
    def get(self, id=None):
        if id is None:
            # Return all pizzas
            return jsonify(pizzas)
        # Return one pizza
        pizza = find_pizza(id)
        return jsonify(pizza)

    def post(self):
        data = request.get_json()
        # Create pizza logic
        return jsonify(new_pizza), 201

    def put(self, id):
        data = request.get_json()
        # Update pizza logic
        return jsonify(updated)

    def delete(self, id):
        # Delete pizza logic
        return '', 204

Registering Class-Based Views

# Create the view
pizza_view = PizzaAPI.as_view('pizza_api')

# Register routes
app.add_url_rule(
    '/pizzas',
    view_func=pizza_view,
    methods=['GET', 'POST']
)
app.add_url_rule(
    '/pizzas/<int:id>',
    view_func=pizza_view,
    methods=['GET', 'PUT', 'DELETE']
)

🎯 Why Use Class-Based Views?

Benefit Explanation
Organization All pizza code in one place
Inheritance Share logic between views
Clarity Method names match HTTP methods
Reusability Easy to extend and modify
graph TD A["PizzaAPI Class"] --> B["get - Read pizzas"] A --> C["post - Create pizza"] A --> D["put - Update pizza"] A --> E["delete - Remove pizza"]

πŸŽ‰ Putting It All Together

Let’s build a complete mini pizza API!

from flask import Flask, request, jsonify
from flask.views import MethodView

app = Flask(__name__)

# Our pizza database
pizzas = []
next_id = 1

class PizzaAPI(MethodView):

    def get(self, id=None):
        if id is None:
            return jsonify({
                "data": pizzas,
                "count": len(pizzas)
            }), 200

        pizza = next(
            (p for p in pizzas
             if p['id'] == id), None
        )
        if not pizza:
            return jsonify({
                "error": "Not found"
            }), 404

        return jsonify(pizza), 200

    def post(self):
        global next_id
        data = request.get_json()

        if not data or 'name' not in data:
            return jsonify({
                "error": "Name required"
            }), 400

        pizza = {
            "id": next_id,
            "name": data['name']
        }
        next_id += 1
        pizzas.append(pizza)

        return jsonify(pizza), 201

    def delete(self, id):
        global pizzas
        pizzas = [p for p in pizzas
                  if p['id'] != id]
        return '', 204

# Register routes
view = PizzaAPI.as_view('pizza')
app.add_url_rule('/pizzas',
    view_func=view,
    methods=['GET', 'POST'])
app.add_url_rule('/pizzas/<int:id>',
    view_func=view,
    methods=['GET', 'DELETE'])

if __name__ == '__main__':
    app.run(debug=True)

πŸš€ You Did It!

You’ve learned how to:

  • βœ… Design clean API endpoints
  • βœ… Use the right HTTP status codes
  • βœ… Handle JSON requests and responses
  • βœ… Create helpful error messages
  • βœ… Validate incoming data
  • βœ… Organize code with class-based views

You’re now ready to build APIs that are:

  • πŸ• Easy to understand
  • πŸ• Easy to use
  • πŸ• Easy to maintain

Go forth and build amazing things! πŸŽ‰

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.