APIs - CameronAuler/python-devops GitHub Wiki
When designing APIs, it's essential to follow architectural principles that ensure scalability, maintainability, and efficiency. This is where the REST (Representational State Transfer) architecture comes in. However, not all APIs that claim to be "REST" fully adhere to REST principlesβsome are RESTful, while others are RESTless.
- β Scalability: Follows industry standards and can handle high traffic.
- β Interoperability: Can be used by multiple clients (web, mobile, CLI).
- β Security: Uses proper authentication (JWT, OAuth, API keys).
- β Maintainability: Easier to update and extend without breaking compatibility.
A RESTful API adheres to REST architectural constraints, meaning:
- β
Uses correct HTTP methods for different operations:
-
GET
β Retrieve data -
POST
β Create a new resource -
PUT/PATCH
β Update an existing resource -
DELETE
β Remove a resource
-
- β
Uses resource-based URLs rather than action-based URLs:
- β
GET /users/
(fetch all users) - β
GET /users/123/
(fetch a specific user) - β
POST /users/
(create a new user)
- β
- β Stateless communication β Each request must contain all necessary information, and the server does not store client state between requests.
- β Follows client-server separation β The API and the client consuming it are independent, allowing different clients (web, mobile, CLI) to interact with the API without modifications.
- β Uses JSON (or XML) as the standard response format, ensuring interoperability.
- β Supports HATEOAS (Hypermedia as the Engine of Application State) β API responses include links to related resources for navigation.
from django.db import models
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.username
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
from rest_framework import generics
from .models import User
from .serializers import UserSerializer
# Endpoint: GET /users/ | POST /users/
class UserListCreateView(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
# Endpoint: GET /users/<id>/ | PUT/PATCH /users/<id>/ | DELETE /users/<id>/
class UserDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
from django.urls import path
from .views import UserListCreateView, UserDetailView
urlpatterns = [
path("users/", UserListCreateView.as_view(), name="user-list-create"),
path("users/<int:pk>/", UserDetailView.as_view(), name="user-detail"),
]
- β
Now, we have a fully RESTful API with:
-
GET /users/
β Fetch all users -
POST /users/
β Create a new user -
GET /users/123/
β Fetch a single user -
PUT/PATCH /users/123/
β Update user info -
DELETE /users/123/
β Remove a user
-
A RESTless API does not fully follow REST principles, meaning:
- β Uses incorrect HTTP methods (e.g., using POST for data retrieval).
- β Uses action-based URLs instead of resource-based URLs:
- β POST /getUserData instead of GET /users/123/
- β POST /updateUser instead of PUT /users/123/
- β Not stateless β Some RESTless APIs rely on server-side sessions instead of passing authentication tokens.
- β Inconsistent data formats β Responses may not follow a standard like JSON, leading to compatibility issues.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/getUserData", methods=["POST"]) # β Wrong: Should use GET instead of POST
def get_user_data():
"""Returns mock user data"""
return jsonify({"username": "JohnDoe", "email": "[email protected]"})
@app.route("/updateUser", methods=["POST"]) # β Wrong: Should use PUT or PATCH
def update_user():
"""Mock update user function"""
data = request.json
return jsonify({"message": "User updated", "updated_data": data})
if __name__ == "__main__":
app.run()
Problems in this Flask API:
- β Uses POST for retrieving data instead of GET.
- β Uses POST for updating data instead of PUT/PATCH.
- β Uses action-based URLs (/getUserData and /updateUser) instead of resource-based URLs.
@app.route("/users/<int:user_id>", methods=["GET", "PUT"])
def user(user_id):
if request.method == "GET":
return jsonify({"id": user_id, "username": "JohnDoe"})
if request.method == "PUT":
data = request.json
return jsonify({"message": "User updated", "updated_data": data})
β Now follows REST principles:
-
GET /users/123/
β Fetch user -
PUT /users/123/
β Update user
pip install django djangorestframework
install_requires=[
...,
"Django",
"djangorestframework",
]
cd src/example_python_project/api/
django-admin startproject django_app .
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework", # Add Django REST Framework
]
Find the allowed hosts entry and add '*'
ALLOWED_HOSTS = ['*']
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
NOTE: Ensure you are still in the src/example_python_project/api/
directory
python manage.py startapp rest_api
This command will create:
src/example_python_project/api/rest_api/
src/example_python_project/api/rest_api/views.py
from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def health_check(request):
"""Health check endpoint for the Django API."""
return JsonResponse({"status": "OK"})
src/example_python_project/api/rest_api/urls.py
(May need to be created manually)
from django.urls import path
from .views import health_check
urlpatterns = [
path("health/", health_check, name="health_check"),
]
src/example_python_project/api/django_app/urls.py
"""
URL configuration for django_app project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("example_python_project.api.rest_api.urls")), # Include REST API routes
]
NOTE: Ensure you are still in the src/example_python_project/api/
directory
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser # Follow the prompts
python manage.py runserver
- visit:
http://127.0.0.1:8000/admin/
- visit:
http://127.0.0.1:8000/api/health/