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:
- Order resource with HATEOAS links
- OpenAPI documentation endpoint
- Cursor-based pagination for orders
- Role-based authorization (admin vs user)
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro