Skip to content

Restful Project

DodaTech 3 min read

title: "RESTful Project — Build a Complete REST API" description: "Build a complete REST API for an e-commerce system with users, products, orders, and payments following all RESTful design principles and best practices." date: 2026-06-28 lastmod: 2026-06-28 weight: 32 tags: [apis, restful] }

Build a complete REST API for an e-commerce platform implementing users, products, orders, and payments with proper resource design, middleware, and documentation.

What You'll Learn

  • Building a full REST API from scratch
  • Implementing all RESTful patterns together
  • API testing and documentation

Why It Matters

This project combines all RESTful concepts into a production-ready API you can use as a reference for any REST API project.

Project Structure

# app.py - Complete RESTful e-commerce API
from flask import Flask, request, jsonify, g
from functools import wraps
import uuid, jwt, time

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'

# In-memory database (replace with real DB in production)
db = {
    'users': {},
    'products': {},
    'orders': {},
    'next_id': {'users': 1, 'products': 1, 'orders': 1}
}

# --- Middleware ---
@app.before_request
def before_request():
    g.request_id = str(uuid.uuid4())
    g.start_time = time.time()

@app.after_request
def after_request(response):
    response.headers['X-Request-Id'] = g.request_id
    response.headers['X-Content-Type-Options'] = 'nosniff'
    elapsed = time.time() - g.start_time
    app.logger.info(f"{request.method} {request.path} {response.status_code} {elapsed:.3f}s")
    return response

def require_auth(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        auth = request.headers.get('Authorization', '')
        if not auth.startswith('Bearer '):
            return jsonify({"error": "Auth required"}), 401
        try:
            payload = jwt.decode(auth[7:], app.config['SECRET_KEY'], algorithms=['HS256'])
            g.user_id = payload['user_id']
        except jwt.InvalidTokenError:
            return jsonify({"error": "Invalid token"}), 401
        return f(*args, **kwargs)
    return wrapper

# --- Resources ---
@app.route('/api/auth/login', methods=['POST'])
def login():
    data = request.json
    user = next((u for u in db['users'].values() if u['email'] == data.get('email')), None)
    if not user:
        return jsonify({"error": "Invalid credentials"}), 401
    token = jwt.encode({'user_id': user['id']}, app.config['SECRET_KEY'], algorithm='HS256')
    return jsonify({"token": token, "token_type": "Bearer"})

@app.route('/api/users', methods=['GET'])
@require_auth
def list_users():
    return jsonify([{"id": u['id'], "name": u['name'], "email": u['email']}
                    for u in db['users'].values()])

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.json
    if not data.get('email') or not data.get('name'):
        return jsonify({"error": "name and email required"}), 422
    uid = db['next_id']['users']
    db['users'][uid] = {'id': uid, 'name': data['name'], 'email': data['email']}
    db['next_id']['users'] += 1
    resp = jsonify(db['users'][uid])
    resp.status_code = 201
    resp.headers['Location'] = f'/api/users/{uid}'
    return resp

@app.route('/api/products', methods=['GET'])
def list_products():
    page = request.args.get('page', 1, type=int)
    limit = min(request.args.get('limit', 20, type=int), 100)
    products = list(db['products'].values())
    total = len(products)
    start = (page - 1) * limit
    return jsonify({
        "data": products[start:start+limit],
        "pagination": {"page": page, "limit": limit, "total": total}
    })

@app.route('/api/products/<int:id>', methods=['GET'])
def get_product(id):
    product = db['products'].get(id)
    if not product:
        return jsonify({"error": "Product not found"}), 404
    return jsonify(product)

Testing

def test_ecommerce_api():
    client = app.test_client()

    # Create user
    resp = client.post('/api/users', json={'name': 'Alice', 'email': 'alice@test.com'})
    assert resp.status_code == 201
    assert 'Location' in resp.headers

    # Login
    resp = client.post('/api/auth/login', json={'email': 'alice@test.com'})
    assert resp.status_code == 200
    token = resp.json['token']

    # List users (authenticated)
    resp = client.get('/api/users', headers={'Authorization': f'Bearer {token}'})
    assert resp.status_code == 200

    # List users (unauthenticated)
    resp = client.get('/api/users')
    assert resp.status_code == 401

    # Test pagination
    for i in range(50):
        pid = db['next_id']['products']
        db['products'][pid] = {'id': pid, 'name': f'Product {i}'}
        db['next_id']['products'] += 1

    resp = client.get('/api/products?page=2&limit=10')
    assert resp.status_code == 200
    assert resp.json['pagination']['page'] == 2
    assert len(resp.json['data']) == 10

Challenge

Extend this e-commerce API with:

  1. Order resource with HATEOAS links
  2. OpenAPI documentation endpoint
  3. Cursor-based pagination for orders
  4. Role-based authorization (admin vs user)

FAQ

How do I deploy this API?

: Package with Docker and deploy behind a reverse proxy (Nginx) with TLS.

How do I connect a real database?

: Replace the in-memory db dictionary with SQLAlchemy or another ORM.

Should I use this in production?

: This is a learning project. Add proper auth, database, and error handling for production.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro