Python and REST APIs: Interacting with Web Services Using Python and REST APIs

Author: Abhilash Puli | October 02, 2025



Table of Contents

Table of Contents

  1. What is API,API Stands for,Working?
  2. REST Architectural Constraints
  3. What are the Benefits of REST APIs?
  4. REST APIs and Web Services
  5. HTTP Methods
  6. Status Codes
  7. API Endpoints

9. REST and Python: Building APIs

10. REST and Python: Tools of the Trade

11. Conclusion

Prerequisites


What is an API?

APIs are mechanisms that enable two software components to communicate with each other using a set of definitions and protocols. For example, the weather bureau’s software system contains daily weather data. The weather app on your phone “talks” to this system via APIs and shows you daily weather updates on your phone.

What does API stand for?

API stands for Application Programming Interface. In the context of APIs, the word Application refers to any software with a distinct function. Interface can be thought of as a contract of service between two applications. This contract defines how the two communicate with each other using requests and responses. Their API documentation contains information on how developers are to structure those requests and responses.

Types of APIs

APIs can be categorized in several ways, but one common approach is based on who has access to them. The main categories include:

  1. Private APIs (Internal APIs)

2. Public APIs (Open APIs)

3. Partner APIs

Types of APIs
Types of APIs

How do APIs work?

API architecture is usually explained in terms of client and server. The application sending the request is called the client, and the application sending the response is called the server. So in the weather example, the bureau’s weather database is the server, and the mobile app is the client.

There are four different ways that APIs can work depending on when and why they were created.

Consider a book-distributing company. Instead of building a full-fledged cloud app for bookstore clerks to check inventory — which could be costly, platform-dependent, and time-consuming to maintain — the company could expose a REST API for stock availability.

The advantages of this approach are significant:

  1. Centralized data access: Customers can aggregate inventory information from multiple distributors in one place.
  2. Internal flexibility: The distributor can modify internal systems without breaking customer workflows, as long as the API contract remains stable.
  3. Ecosystem growth: Third-party developers can build apps that leverage the API, enhancing customer experience and potentially driving higher sales and new business opportunities.

This example highlights how APIs not only streamline operations but also create value for both the provider and its customers.

SOAP APIs

These APIs use Simple Object Access Protocol. Client and server exchange messages using XML. This is a less flexible API that was more popular in the past.

RPC APIs

These APIs are called Remote Procedure Calls. The client completes a function (or procedure) on the server, and the server sends the output back to the client.

Websocket APIs

Websocket API is another modern web API development that uses JSON objects to pass data. A WebSocket API supports two-way communication between client apps and the server. The server can send callback messages to connected clients, making it more efficient than REST API.

REST APIs

These are the most popular and flexible APIs found on the web today. The client sends requests to the server as data. The server uses this client input to start internal functions and returns output data back to the client. Let’s look at REST APIs in more detail below.

The web is overflowing with data — and much of it is up for grabs if you know how to ask. Platforms like YouTube, GitHub, and X — Formerly Twitter( expose their data to developers through APIs (Application Programming Interfaces). Among the many ways to design APIs, the REST (Representational State Transfer) architecture style has become the go-to standard.

Python makes working with REST APIs especially painless. Whether you want to consume existing APIs (pulling data into your projects) or build your own REST API from scratch, Python has the right tools to make it happen.

By the end of this tutorial, you’ll understand:


REST (Representational State Transfer) is a set of architectural constraints designed to create efficient, reliable, and scalable distributed systems.

At its core, REST treats everything as a resource (for example, a document, image, or user record) that can be accessed and manipulated through standardized, language-agnostic client–server interactions.

An API is considered RESTful if it follows these constraints. In practice, though, many HTTP APIs are labeled “RESTful” even if they don’t strictly comply. For beginners, you can think of a REST API simply as an HTTP service that can be called using standard web tools and libraries.

REST API Model:

REST API Model

REST architectural constraints:

Important: REST is not a strict specification. It’s a set of guidelines and principles for designing networked systems.

What are the benefits of REST APIs?

REST APIs offer four main benefits:

1. Integration

APIs are used to integrate new applications with existing software systems. This increases development speed because each functionality doesn’t have to be written from scratch. You can use APIs to leverage existing code.

2. Innovation

Entire industries can change with the arrival of a new app. Businesses need to respond quickly and support the rapid deployment of innovative services. They can do this by making changes at the API level without having to re-write the whole code.

3. Expansion

APIs present a unique opportunity for businesses to meet their clients’ needs across different platforms. For example, maps API allows map information integration via websites, Android,iOS, etc. Any business can give similar access to their internal databases by using free or paid APIs.

4. Ease of maintenance

The API acts as a gateway between two systems. Each system is obliged to make internal changes so that the API is not impacted. This way, any future code changes by one party do not impact the other party.

REST APIs and Web Services

A REST web service is simply a web service that follows REST’s architectural constraints. These services make their data available to external applications through an API (Application Programming Interface).

A REST API provides access to this data via public web URLs, typically returning results in formats like JSON or XML.

For example, GitHub exposes its data through a REST API. Here’s one of its endpoints:

https://api.github.com/users/octocat/repos

This URL gives you access to information about a specific GitHub user. To interact with a REST API, you send an HTTP request to a particular URL (called an endpoint) and then process the response the server sends back.

For instance, a simple GET request to the GitHub API endpoint:

https://api.github.com/users/octocat

HTTP Methods

REST APIs rely on HTTP methods (also called verbs) such as GET, POST, and DELETE to determine what kind of action to perform on a resource.

A resource is any piece of data the web service exposes — for example, a user profile, an image, or a list of products. The HTTP method specifies how the API should interact with that resource: retrieve it, create it, update it, or remove it.

Although the HTTP standard defines many methods, the following five are the most commonly used in REST APIs:

HTTP standard methods

The following table lists HTTP request methods and their categorization in terms of safety, cacheability, and idempotency.

HTTP response status code Table
HTTP request methods and their categorization

(Reference)

Status Codes

When a REST API processes an HTTP request, it sends back an HTTP response. Every response includes a status code, which tells you the outcome of the request.

Status codes are crucial because they let applications decide what to do next — for example, show a success message, retry the request, or handle an error gracefully.

Here are the most common status codes you’ll see when working with REST APIs:

statusCodeCategoryTable
common status codes (Category Based)

These ten status codes represent only a small subset of the available HTTP status codes. Each code belongs to a category based on the first digit:

statusCodeCategoryTable
common status codes (Category Based)

HTTP status codes come in handy when working with REST APIs as you’ll often need to perform different logic based on the results of the request.

API Endpoints

A REST API provides a set of public URLs that client applications can use to access and interact with the resources of a web service. These URLs are called endpoints. Each endpoint corresponds to a specific function or resource within the API.

For example, consider a library management system. Below is a table showing API endpoints for managing the Book resource hypothetical CRM system, which represents the books available in the library:

Common REST API endpoints for managing books in a library system

Each of the endpoints above performs a different action based on the HTTP method.

Note: The base URL for the endpoints has been omitted here for brevity. In practice, you’ll need the full URL to access an API endpoint.

For example:

https://api.example.com/books

This is the full URL for the books resource. The base URL is everything before /books (in this case, https://api.example.com).

You’ll notice that some endpoints include <book_id> at the end. This notation means you need to append a numeric book ID to the URL to tell the REST API exactly which book you want to work with.

Also, keep in mind that the endpoints shown above represent just one resource in the system. In a real-world, production-ready REST API, there could be dozens or even hundreds of endpoints, each managing different resources and operations within the web service.

Note: An endpoint shouldn’t contain verbs. Instead, you should select the appropriate HTTP methods to convey the endpoint’s action. For example, the endpoint below contains an unneeded verb:

GET /getBooks

Here, get is included in the endpoint when it isn’t needed. The HTTP method GET already provides the semantic meaning for the endpoint by indicating the action. You can remove get from the endpoint:

GET /books

This endpoint contains only a plural noun, and the HTTP method GET communicates the action.

Example: Nested Resources

Sometimes a resource exists within another resource, creating a hierarchy. For instance, a library system might manage chapters nested under books. Here’s how the endpoints could look:

Nested Resources Table
Hierarchical API design: Handling chapters under books.

With these endpoints, you can manage chapters for a specific book in the system.

This isn’t the only way to structure nested resources. Some developers prefer using query strings to access a nested resource. A query string lets you send additional parameters with your HTTP request.

For example, you could fetch chapters for a specific book using a query string like this:

GET /chapters?book_id=214

Here, the query string parameter book_id=214 specifies that you want to retrieve only the chapters belonging to book 214. This approach can be useful for filtering or searching, but it’s important to balance readability and RESTful conventions when designing your API.

Note: It’s very unlikely that a REST API will remain unchanged throughout the life of a web service. Resources evolve, and endpoints may need updates to reflect these changes. This is where API versioning comes in — it allows you to modify your API without breaking existing integrations.

There are several popular strategies for versioning an API, and the right choice depends on your specific requirements:

URI versioning: Include the version number in the URL, e.g., /v1/books.

HTTP header versioning: Specify the version in request headers.

Query string versioning: Use query parameters like ?version=1.

Media type versioning: Include version information in the Content-Type or Accept headers.

Regardless of the method, API versioning is essential for ensuring your API can adapt to changing requirements while continuing to support existing users.

Now that you’ve covered endpoints, in the next section you’ll look at some options for formatting data in your REST API.

Pick Your Data Interchange Format

When designing a REST API, you need to decide how your data will be formatted. Two common options are XML and JSON. Historically, XML was widely used with SOAP APIs, but JSON has become the preferred format for REST APIs because it’s lightweight, easy to read, and integrates seamlessly with JavaScript and most programming languages.

To illustrate, here’s an example of a book resource formatted as XML:

<?xml version="1.0" encoding="UTF-8"?>
<book>
    <title>Deep Learning Essentials</title>
    <page_count>420</page_count>
    <pub_date>2022-08-10</pub_date>
    <authors>
        <author>
            <name>Aurélien Géron</name>
        </author>
        <author>
            <name>Francois Chollet</name>
        </author>
    </authors>
    <isbn13>978-1617296864</isbn13>
    <genre>Technology</genre>
</book>

Here, the XML structure uses nested elements to represent hierarchical data, such as multiple authors for a book. While XML is very expressive, it tends to be more verbose than JSON, which is why many REST APIs today favor JSON.

Now, here’s the same book represented in JSON:

{
    "title": "Deep Learning Essentials",
    "page_count": 420,
    "pub_date": "2022-08-10",
    "authors": [
        {"name": "Aurélien Géron"},
        {"name": "Francois Chollet"}
    ],
    "isbn13": "978-1617296864",
    "genre": "Technology"
}

JSON stores data in key-value pairs, similar to a Python dictionary. Like XML, JSON supports nested structures, allowing you to model complex relationships, such as multiple authors for a single book.

While neither JSON nor XML is inherently “better,” JSON is generally preferred in REST APIs, especially when paired with front-end frameworks like React, Vue, or Angular. Its simplicity, readability, and native compatibility with JavaScript make it the go-to choice for modern web development.

Design Success Responses

Once you’ve chosen a data format, the next step is deciding how your API will respond to HTTP requests. All responses should follow a consistent structure and include the appropriate HTTP status code.

Let’s look at an example of a GET request to /books in a library API. We’ll examine the raw HTTP request and response rather than using a library like requests for clarity:

HTTP Request:

GET /books HTTP/3
Host: api.example.com

This request has four parts:

  1. GET – the HTTP method.
  2. /books – the API endpoint.
  3. HTTP/1.1 – the HTTP version.
  4. Host: api.example.com – the API host.

HTTP Response:

HTTP/3 200 OK
Content-Type: application/json
[...]
[
    {
        "id": 1,
        "title": "Deep Learning Essentials",
        "author": "Aurélien Géron",
        "published_year": 2022,
        "isbn": "978-1617296864",
        "genre": "Technology"
    },
    {
        "id": 2,
        "title": "Clean Code",
        "author": "Robert C. Martin",
        "published_year": 2008,
        "isbn": "978-0132350884",
        "genre": "Software"
    },
    {
        "id": 3,
        "title": "Artificial Intelligence: A Modern Approach",
        "author": "Stuart Russell",
        "published_year": 2016,
        "isbn": "978-0134610993",
        "genre": "Education"
    }
]

Here’s what you can learn from this response:

By maintaining a consistent response structure, your API becomes predictable, reliable, and easier to consume for developers.

Additionally, always include an appropriate HTTP status code. For a successful GET request, return 200 OK — this communicates clearly that the request was processed successfully and the data is valid.

Take a look at another GET request, this time for a single book:

HTTP Request:

GET /books/1 HTTP/3
Host: api.example.com

This request queries the API for book with ID 1.

HTTP Response:

HTTP/3 200 OK
Content-Type: application/json
{
    "id": 1,
    "title": "Deep Learning Essentials",
    "author": "Aurélien Géron",
    "published_year": 2022,
    "isbn": "978-1617296864",
    "genre": "Technology"
}

Here, the response contains a single JSON object with the book’s data. Since it’s a single object, there’s no need to wrap it in a list. As before, the 200 OK status code confirms that the request was successful.

Note: A GET request should never modify an existing resource. If the request contains data, then this data should be ignored and the API should return the resource unchanged.

Next, let’s look at a POST request to add a new book:

HTTP Request:

POST /books HTTP/3
Host: api.example.com
Content-Type: application/json
{
    "title": "Python for Data Analysis",
    "author": "Wes McKinney",
    "published_year": 2018,
    "isbn": "978-1491957660",
    "genre": "Data Science"
}

This POST request includes JSON data for the new book and sets the Content-Type header to application/json so the API knows how to interpret the request. The API will create a new book using this data.

HTTP Response:

HTTP/3 201 Created
Content-Type: application/json
{
    "id": 4,
    "title": "Python for Data Analysis",
    "author": "Wes McKinney",
    "published_year": 2018,
    "isbn": "978-1491957660",
    "genre": "Data Science"
}

Key points from this response:

Note: It’s important to always send back a copy of a resource when a user creates it with POST or modifies it with PUT or PATCH. This way, the user can see the changes that they’ve made.

Now, let’s look at a PUT request to update a book:

HTTP Request:

PUT /books/4 HTTP/3
Host: api.example.com
Content-Type: application/json
{
    "title": "Advanced Python Programming",
    "author": "Joe Marini",
    "published_year": 2021,
    "isbn": "978-1492051376",
    "genre": "Programming"
}

This request uses the id of the previously created book to replace all its fields with new data. Remember, PUT replaces the entire resource, so every field should be included in the request.

HTTP Response:

HTTP/3 200 OK
Content-Type: application/json
{
    "id": 4,
    "title": "Advanced Python Programming",
    "author": "Joe Marini",
    "published_year": 2021,
    "isbn": "978-1492051376",
    "genre": "Programming"
}

The 200 OK status code confirms that the update was successful, and the response body returns the updated book.

The same concept applies to a PATCH request, which is used to update only part of a resource:

HTTP Request:

PATCH /books/4 HTTP/3
Host: api.example.com
Content-Type: application/json
{
    "published_year": 2022,
    "genre": "Software Development"
}

This request updates only the published_year and genre fields of the book with ID 4, leaving all other fields unchanged.

HTTP Response:

HTTP/3 200 OK
Content-Type: application/json
{
    "id": 4,
    "title": "Advanced Python Programming",
    "author": "Joe Marini",
    "published_year": 2022,
    "isbn": "978-1492051376",
    "genre": "Software Development"
}

The response returns a full copy of the book, but only the fields included in the PATCH request (published_year and genre) have been updated.

Finally, let’s see how a REST API should respond to a DELETE request. Here’s an example to remove a book:

HTTP Request:

DELETE /books/4 HTTP/3
Host: api.example.com

This request instructs the API to delete the book with ID 4.

HTTP Response:

HTTP/3 204 No Content

The response includes only the 204 No Content status code, which indicates that the operation was successful but no content is returned. This makes sense because the book has been deleted — there’s no need to send it back.

Design Error Responses

Even the best-designed APIs can encounter errors. It’s important to define a consistent error response format that includes both a descriptive message and the appropriate HTTP status code.

For example, consider a request for a resource that doesn’t exist in the API:

HTTP Request:

GET /magazines HTTP/3
Host: api.example.com

Here, the client requests the /magazines endpoint, which doesn’t exist.

HTTP Response:

HTTP/3 404 Not Found
Content-Type: application/json
{
    "error": "The requested resource was not found."
}

Next, consider an error response when a client sends an invalid request:

HTTP Request:

POST /books HTTP/3
Host: api.example.com
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<book>
    <title>Python Crash Course</title>
    <author>Eric Matthes</author>
    <published_year>2019</published_year>
    <genre>Programming</genre>
</book>

HTTP Response:

HTTP/3 415 Unsupported Media Type
Content-Type: application/json
{
    "error": "The application/xml media type is not supported."
}

Invalid Data in Correct Format:
Even if the JSON is valid, the API might reject unexpected or unsupported fields:

HTTP Request:

POST /books HTTP/3
Host: api.example.com
Content-Type: application/json
{
    "title": "Python Crash Course",
    "author": "Eric Matthes",
    "pages": 550,
    "extraField": "Invalid data"
}

HTTP Response:

HTTP/3 422 Unprocessable Entity
Content-Type: application/json
{
    "error": "Request had invalid or missing data."
}

Responding to requests — both successful and erroneous — is one of the most important jobs of a REST API. An intuitive API with accurate responses makes it much easier for developers to build applications around your web service. Fortunately, several Python web frameworks handle much of the complexity of processing HTTP requests and returning structured responses. In the next section, we’ll explore three popular frameworks that simplify building REST APIs.


REST and Python: Tools of the Trade

In this section, we’ll explore three popular Python frameworks for building REST APIs. Each framework has its strengths and trade-offs, so you’ll need to decide which one best suits your project. To demonstrate, we’ll build a similar API in each framework that manages a collection of books.

Each book will have the following fields:

These fields store essential information about each book.

Most of the time, REST APIs fetch data from a database, but connecting to a database is beyond the scope of this tutorial. For our examples, we’ll store the data in a Python list, except for the Django REST framework example, which uses Django’s built-in SQLite database.

This approach keeps the examples simple and focused, letting you learn the framework mechanics before adding database complexity.

Note: It’s a good practice to create separate folders for each framework example to keep the source files organized. Additionally, you should use Python virtual environments for each project to isolate dependencies and avoid conflicts between frameworks.

To keep things consistent, we’ll use books as the main endpoint for all three frameworks. We’ll also use JSON as the data format across the examples.

Now that the background is set, let’s dive into building a REST API in Flask.

Flask

Flask is a lightweight Python microframework for building web applications and REST APIs. It provides a solid foundation while giving you the flexibility to structure your application the way you want. At its core, Flask handles HTTP requests and routes them to the appropriate function in your application.

For our example, the API will manage a collection of books, each with the fields:

We’ll store these books in a Python list for simplicity, allowing us to focus on Flask mechanics without worrying about databases.

For example, instead of using:

@app.get("/books")
@app.post("/books")

in Flask 1.x, you should use:

@app.route("/books")
@app.route("/books", methods=["POST"])

This handles GET and POST requests to the /books endpoint in older Flask versions.

Example Flask Application: Books API

# app.py
from flask import Flask, request, jsonify

app = Flask(__name__)

books = [
    {"id": 1, "title": "Python Crash Course", "author": "Eric Matthes", "pages": 550},
    {"id": 2, "title": "Automate the Boring Stuff", "author": "Al Sweigart", "pages": 600},
    {"id": 3, "title": "Fluent Python", "author": "Luciano Ramalho", "pages": 790},
]

def _find_next_id():
    return max(book["id"] for book in books) + 1

@app.get("/books")
def get_books():
    return jsonify(books)

@app.post("/books")
def add_book():
    if request.is_json:
        book = request.get_json()
        book["id"] = _find_next_id()
        books.append(book)
        return book, 201
    return {"error": "Request must be JSON"}, 415

How It Works

Note: This Flask application includes functions to handle only two types of requests to the API endpoint, /countries. In a full REST API, you’d want to expand this to include functions for all the required operations.

You can try out this application by first installing Flask with pip:

$ python -m pip install flask

Once installed, save the code in a file called app.py. To run the Flask application, you need to set an environment variable called FLASK_APP pointing to app.py. This tells Flask which file contains your application.

On macOS or Linux, run:

$ export FLASK_APP=app.py

Optionally, you can enable debug mode to get helpful error messages and automatic reloads whenever you change your code:

$ export FLASK_ENV=development

Note: The above commands work on macOS or Linux. If you’re running this on Windows, then you need to set FLASK_APP and FLASK_ENV like this in the Command Prompt:

C:\>set FLASK_APP=app.py
C:\>set FLASK_ENV=development

Now FLASK_APP and FLASK_ENV are set inside the Windows shell.

With all environment variables set, you can start the Flask development server by running:

$ flask run

You’ll see output like this:

* Serving Flask app "app.py" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

This launches a server running your application. Open your browser and navigate to http://127.0.0.1:5000/books, and you’ll see the following JSON response:

[
    {"id": 1, "title": "Python Crash Course", "author": "Eric Matthes", "pages": 550},
    {"id": 2, "title": "Automate the Boring Stuff", "author": "Al Sweigart", "pages": 600},
    {"id": 3, "title": "Fluent Python", "author": "Luciano Ramalho", "pages": 790}
]

This response contains the three books defined at the start of app.py.

Here’s how it works in the code:

@app.get("/books")
def get_books():
    return jsonify(books)

The @app.get() decorator connects GET requests to the get_books() function. When you access /books, Flask calls this function to handle the request and return a JSON response with all the book data.

In the code above, get_books() takes books, which is a Python list, and converts it to JSON using jsonify(). This JSON is then returned in the HTTP response.

The helper function _find_next_id() determines the ID for the new book:

def _find_next_id():
    return max(book["id"] for book in books) + 1

This function uses a generator expression to select all existing book IDs and then finds the largest value. Adding 1 gives the next available ID.

You can test this endpoint using curl from the command line to simulate a POST request:

$ curl -i http://127.0.0.1:5000/books \
-X POST \
-H 'Content-Type: application/json' \
-d '{"title":"Clean Code", "author":"Robert C. Martin", "pages":364}'

The response will look like this:

HTTP/3 201 CREATED
Content-Type: application/json
...
{"id": 4, "title":"Clean Code", "author":"Robert C. Martin", "pages":364}

Curl options to note:

With these options, curl sends JSON data in a POST request, and the API returns 201 Created along with the JSON for the new book.

Django REST Framework

Another popular choice for building REST APIs in Python is Django REST framework (DRF). DRF is a plugin for Django that adds powerful REST API functionality on top of an existing Django project.

To use Django REST framework, you first need a Django project. If you already have one, you can integrate DRF directly. Otherwise, follow along to create a new project and add DRF.

Start by installing Django and Django REST framework:

$ python -m pip install Django djangorestframework

Once installed, create a new Django project:

$ django-admin startproject bookapi

This creates a bookapi folder in your current directory containing all the necessary files for the project. Next, create a new Django application to handle your books API. In Django, each app manages a distinct part of the project:

$ cd bookapi
$ python manage.py startapp books

This creates a books folder inside your project. Inside it, you’ll find the base files for the application.

Finally, tell Django about your new app. Open bookapi/settings.py and add "books" and "rest_framework" to INSTALLED_APPS:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework",
    "books",
]

The next step is to create a Django model to define the fields of your data. Inside the books application, update models.py:

# books/models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    page_count = models.IntegerField(help_text="Number of pages")
    published_date = models.DateField()

This defines a Book model. Django will use this to create the database table and columns for your book data.

Run the following commands to update the database:

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0001_initial.py
    - Create model Book
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, books, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
Applying books.0001_initial... OK

These commands create a new database table for books.

The table starts empty, but it’s useful to have some initial data for testing DRF. You can use a Django fixture to load sample books. Save the following JSON as books.json inside the books directory:

[
    {
        "model": "books.book",
        "pk": 1,
        "fields": {
            "title": "Python Basics",
            "author": "David Amos",
            "page_count": 635,
            "published_date": "2021-03-16"
        }
    },
    {
        "model": "books.book",
        "pk": 2,
        "fields": {
            "title": "Data Science Fundamentals",
            "author": "Joanna Jablonski",
            "page_count": 512,
            "published_date": "2020-07-10"
        }
    },
    {
        "model": "books.book",
        "pk": 3,
        "fields": {
            "title": "Machine Learning 101",
            "author": "Dan Bader",
            "page_count": 400,
            "published_date": "2022-01-05"
        }
    }
]

This fixture gives you some initial book records for testing your API. You can later load it with:

$ python manage.py loaddata books.json

This JSON contains database entries for three books. Call the following command to load this data into the database:

$ python manage.py loaddata books.json
Installed 3 object(s) from 1 fixture(s)

This adds three rows to the database.

With that, your Django application is now set up and populated with initial data. You can start integrating Django REST framework.

Django REST framework converts Django models into JSON for your REST API using serializers. A serializer defines how to transform a model instance into JSON and which fields to include.

Create a file called serializers.py inside the books application. Add the following code:

# books/serializers.py
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ["id", "title", "author", "page_count", "published_date"]

This BookSerializer subclasses serializers.ModelSerializer to automatically generate JSON from the Book model fields. By specifying the fields list, you control exactly which fields are exposed via the API.

Just like Django, Django REST framework uses views to query the database and display data via the API. Instead of manually writing each REST API view, you can subclass ModelViewSet, which comes with built-in views for common REST API operations.

Here’s a table of ModelViewSet actions with their equivalent HTTP methods for a books API:

HTTP-Method-Action Table
REST API Actions Provided by Django REST Framework’s ModelViewSet for the Book Resource

As you can see, these actions map to the standard HTTP methods you’d expect in a REST API. You can override these actions in your subclass or add additional actions based on the requirements of your API.

Below is the code for a ModelViewSet subclass called BookViewSet. This class will generate the views needed to manage Book data. Add the following code to views.py inside the books application:

# books/views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    serializer_class = BookSerializer
    queryset = Book.objects.all()

In this class, serializer_class is set to BookSerializer and queryset is set to Book.objects.all(). This tells Django REST framework which serializer to use and how to query the database for this specific set of views.

Once the views are created, they need to be mapped to the appropriate URLs or endpoints. To do this, Django REST framework provides a DefaultRouter that will automatically generate URLs for a ModelViewSet.

Create a urls.py file in the books application and add the following code to the file:

# books/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r "books", BookViewSet)
urlpatterns = [
    path("", include(router.urls))
]

This code creates a DefaultRouter and registers BookViewSet under the books URL. This will place all the URLs for BookViewSet under /books/.

Finally, you need to update the project’s base urls.py file to include all the books URLs in the project. Update the urls.py file inside your project folder (e.g., bookapi/urls.py) with the following code:

# bookapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("books.urls")),
]

This puts all the URLs under /books/. Now you’re ready to try out your Django-backed REST API. Run the following command in the root bookapi directory to start the Django development server:

$ python manage.py runserver

The development server is now running. Go ahead and send a GET request to /books/ to get a list of all the books in your Django project:

$ curl -i http://127.0.0.1:8000/books/ -w '\n'

Example response:

HTTP/3 200 OK
...
[
    {
        "id": 1,
        "title": "The Pragmatic Programmer",
        "author": "Andrew Hunt",
        "pages": 352
    },
    {
        "id": 2,
        "title": "Clean Code",
        "author": "Robert C. Martin",
        "pages": 464
    },
    {
        "id": 3,
        "title": "Effective Python",
        "author": "Brett Slatkin",
        "pages": 256
    }
]

Django REST framework sends back a JSON response with the three books you added earlier. The response above is formatted for readability, so your response may look slightly different.

The DefaultRouter you created in books/urls.py provides URLs for requests to all the standard API endpoints:

You can try out a POST request to /books/ to create a new book in your Django project:

$ curl -i http://127.0.0.1:8000/books/ \
-X POST \
-H 'Content-Type: application/json' \
-d '{"title":"Python Crash Course", "author":"Eric Matthes", "pages":544}' \
-w '\n'

Example response:

HTTP/3 201 Created
content-type: application/json
...
{"id":4,"title":"Python Crash Course","author":"Eric Matthes","pages":544}

This creates a new Book with the JSON you sent in the request. Django REST framework returns a 201 Created status code along with the newly created book.

FastAPI

FastAPI is a Python web framework optimized for building APIs. It uses Python type hints and has built-in support for async operations. FastAPI is built on top of Starlette and Pydantic and is very performant.

Below is an example of a REST API for books built with FastAPI:

# app.py
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

def _find_next_id():
    return max(book.book_id for book in books) + 1

class Book(BaseModel):
    book_id: int = Field(default_factory=_find_next_id, alias="id")
    title: str
    author: str
    pages: int

books = [
    Book(id=1, title="The Pragmatic Programmer", author="Andrew Hunt", pages=352),
    Book(id=2, title="Clean Code", author="Robert C. Martin", pages=464),
    Book(id=3, title="Deep Learning", author="Ian Goodfellow", pages=775),
]

@app.get("/books")
async def get_books():
    return books

@app.post("/books", status_code=201)
async def add_book(book: Book):
    books.append(book)
    return book

This application uses FastAPI features to build a REST API for books, similar to how we handled countries in previous examples.

You can try this application by installing FastAPI with pip:

$ python -m pip install fastapi

You’ll also need to install uvicorn[standard], a server that can run FastAPI applications:

$ python -m pip install uvicorn[standard]

Once both FastAPI and Uvicorn are installed, save the code above in a file called app.py. Run the following command to start a development server:

$ uvicorn app:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

The server is now running. Open up a browser and go to http://127.0.0.1:8000/books. You’ll see FastAPI respond with this:

[
    {
        "id": 1,
        "title":"The Pragmatic Programmer",
        "author":"Andrew Hunt",
        "pages":352
    },
    {
        "id": 2,
        "title":"Clean Code",
        "author":"Robert C. Martin",
        "pages":464
    },
    {
        "id": 3,
        "title":"Deep Learning",
        "author":"Ian Goodfellow",
        "pages":775
    }
]

FastAPI responds with a JSON array containing a list of books. You can also add a new book by sending a POST request to /books:

$ curl -i http://127.0.0.1:8000/books \
-X POST \
-H 'Content-Type: application/json' \
-d '{"title":"Python Crash Course", "author":"Eric Matthes", "pages":544}' \
-w '\n'
HTTP/3 201 Created
content-type: application/json
...
{"id":4,"title":"Python Crash Course","author":"Eric Matthes","pages":544}

You added a new book. You can confirm this with GET /books:

$ curl -i http://127.0.0.1:8000/books -w '\n'
HTTP/3 200 OK
content-type: application/json
...
[
    {
        "id":1,
        "title":"The Pragmatic Programmer",
        "author":"Andrew Hunt",
        "pages":352
    },
    {
        "id":2,
        "title":"Clean Code",
        "author":"Robert C. Martin",
        "pages":464
    },
    {
        "id":3,
        "title":"Deep Learning",
        "author":"Ian Goodfellow",
        "pages":775
    },
    {
        "id":4,
        "title":"Python Crash Course",
        "author":"Eric Matthes",
        "pages":544
    }
]

FastAPI returns a JSON list including the new book you just added.

FastAPI’s high performance, async support, and automatic documentation generation make it an excellent choice for modern REST APIs.

Conclusion

Building REST APIs in Python has never been more approachable. Whether you choose Flask for its simplicity, Django REST Framework for full-featured projects, or FastAPI for high-performance, modern applications, the core principles remain the same: design intuitive endpoints, validate incoming data, and respond consistently with clear status codes.

Throughout this article, we explored CRUD operations, error handling, data validation, and JSON serialization, highlighting best practices and practical examples. We also saw how frameworks like Pydantic and serializers simplify data validation and ensure your API is robust and maintainable.

By adhering to these standards and leveraging Python’s rich ecosystem, you can build APIs that are scalable, reliable, and developer-friendly, making it easier for clients and applications to interact with your services efficiently.

Ultimately, mastering REST API development is about clarity, consistency, and thoughtful design — tools and frameworks are just enablers. With the foundations laid here, you’re ready to build APIs that are not only functional but also elegant and professional.