RESTful API with Flask

What is Rest?

Rest is short form of REpresentational State Transfer.

Access the full code

Before Diving deep into Rest or Restful API, do you understand what is an API (Application Programming Interface)?

An API is a set of definitions and protocols for building and integrating application software.

An API is a set of rules and protocols that allows different software applications to communicate with each other.

Simple definitions:

  • 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.

  • A software intermediary that allows two applications to talk to each other.

  • Contract between an information provider and an information user—establishing the content required from the consumer (the call) and the content required by the producer (the response).

  • An API is a way for two or more computer programsor components to communicate with each other.

You can think of an API as a mediator between the users or clients and the resources or web services they want to get. It’s also a way for an organization to share resources and information while maintaining security, control, and authentication—determining who gets access to what.

Consider a weather service that provides weather information for different locations. This service might have an API that allows developers to retrieve weather data programmatically.

https://weather-api.com/api/current?location=New+York&apikey=YOUR_API_KEY
  • https://weather-api.com/api/current is the base URL of the API.

  • ?location=New+York is a query parameter specifying the location for which you want to retrieve weather information (in this case, New York).

  • &apikey=YOUR_API_KEY is another query parameter specifying an API key required for authentication and authorization purposes.

    To use this API, a developer could make an HTTP GET request to the above endpoint, providing a valid location and API key. The API would then respond with current weather data for the specified location in a structured format, such as JSON or XML.

    Here's an example of what the response might look like in JSON format:

      {
        "location": "New York",
        "temperature": 20,
        "weather_condition": "Sunny"
      }
    

    Hope you understood what is an API. Back to the REST

REST stands for Representational State Transfer. It's an architectural style for designing networked applications. REST is not a protocol like HTTP or SMTP; rather, it's a set of principles or constraints that guide how resources are defined and addressed over the web. These principles were outlined by Roy Fielding in his doctoral dissertation in 2000.

Here are the key principles of REST:

Client-Server Architecture:

In a RESTful system, the client and server are separate from each other and interact through well-defined interfaces. This separation allows for better scalability and flexibility because the client and server components can evolve independently.

Statelessness:

Each request from a client to the server must contain all the information necessary for the server to understand and fulfill the request. The server does not maintain any client state between requests. This simplifies server implementation and improves scalability.

  1. What is Statelessness?

    Statelessness means that the server does not store any information about the state of the client between requests. In other words, each request from the client to the server must contain all the necessary information for the server to process the request and generate an appropriate response. Once the response is sent back to the client, the server forgets about the request and any associated state.

  2. Why Statelessness Matters?

    Statelessness simplifies server implementation and improves scalability in distributed systems. By not maintaining client state between requests, the server does not need to allocate resources or manage sessions for individual clients. This reduces the complexity of the server-side logic and eliminates the need for server-side state management, leading to more straightforward and efficient server implementations.

  3. Characteristics of Statelessness:

    • Independence of Requests: Each request is independent of any previous requests. The server processes each request in isolation, without relying on any information from previous requests.

    • Client Responsibility: The client is responsible for maintaining its own state between requests. If the client needs to communicate state information to the server, it must include it in the request explicitly.

    • Scalability: Statelessness improves scalability by allowing servers to handle a large number of concurrent clients efficiently. Since the server does not maintain client state, it can easily distribute requests across multiple server instances or scale horizontally without worrying about synchronizing state between servers.

  4. Example of Statelessness in REST APIs:

    In RESTful APIs, statelessness is achieved by designing resources and interactions between clients and servers in a way that each request contains all the information necessary for the server to understand and fulfill the request. Clients interact with resources by sending HTTP requests (e.g., GET, POST, PUT, DELETE) to the server, and the server responds with the requested resource representation.

    For example, when a client sends a GET request to retrieve a resource from a REST API, the request contains all the necessary information (e.g., resource identifier, query parameters) for the server to locate and return the requested resource. The server processes the request, generates the response, and sends it back to the client. Once the response is sent, the server forgets about the request and any associated state, maintaining statelessness.

Cacheability:

Responses from the server can be explicitly marked as cacheable or non-cacheable. Caching allows clients to reuse previously fetched responses, which can improve performance and reduce latency.

  1. What is Caching?

    Caching is a mechanism used to store copies of frequently accessed data or resources in a temporary storage location called a cache. When a client requests data from a server, the server may indicate whether the response can be cached and, if so, for how long it can be stored in the cache.

  2. How Does Caching Work?

    When a client makes a request to a server for a resource, the server typically includes information in the response headers to specify whether the response can be cached and, if so, for how long. If the response is cacheable, the client or intermediate caches along the network path can store a copy of the response data in their local cache.

    The next time the client or another client requests the same resource, instead of forwarding the request to the server, the cache can return the stored copy of the response directly to the client. This helps to reduce the latency and load on the server, as the response can be retrieved much faster from the cache than from the server.

  3. Cacheability in REST APIs:

    In the context of REST APIs, cacheability refers to the ability of a server to specify whether responses to API requests can be cached and, if so, under what conditions. This is typically achieved using HTTP caching mechanisms, such as the Cache-Control header.

    The Cache-Control header allows the server to specify directives that control caching behavior. For example, the server can indicate whether a response can be cached (public or private), how long it can be cached (max-age), whether it can be stored in shared caches (s-maxage), and under what conditions it should be revalidated with the server (must-revalidate, no-cache, etc.).

    By properly configuring cacheability in REST APIs, developers can optimize performance, reduce server load, and improve scalability by allowing clients and intermediary caches to reuse previously fetched responses, rather than repeatedly fetching the same data from the server.

Uniform Interface:

REST APIs use a uniform and standardized set of operations to interact with resources. This includes using standard HTTP methods (GET, POST, PUT, DELETE) to perform CRUD (Create, Read, Update, Delete) operations on resources, and standard HTTP status codes to indicate the outcome of a request.

Layered System:

A RESTful system can be composed of multiple layers, such as proxies, gateways, and load balancers. Each layer hides the complexity of the system from the other layers, which improves scalability and simplifies implementation.

Code-On-Demand (Optional):

This principle is optional in REST. It allows the server to temporarily extend the functionality of a client by transferring executable code, such as JavaScript, to the client. This can enhance the client's capabilities but is rarely used in practice.

Unlocking the Power of Programming Beyond Code: Let's Dive into an Example

Lets beginning by installing the flask.

mkdir movie-app
cd movie-app
# creating virtual environment and activating
python3 -m venv venv
source venv/bin/activate
# Installing the flask
pip install flask

Now that we have Flask installed let's create a simple web application, which we will put in a file called server.py:

#!/usr/bin/python3
from flask import Flask
app = Flask(__name__)

@app.route('/', methods=['GET'])
def hello():
    return "Hello world"

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=3000)

Now run the file server.py:

$ chmod +x server.py
$ ./server.py
 * Running on http://127.0.0.1:3000/
 * Restarting with reloader

You can launch your web browser to see the tiny application http://127.0.0.1:3000/

Now it is time to convert the above simple tiny project into being RESTful

Implementing RESTful services in Flask

Implement the first entry point of our web service:

#!/usr/bin/python3
from flask import Flask, jsonify
app = Flask(__name__)

movies = [
           {
                'id': 1,
                'title': "The Shawshank Redemption",
                'director': "Frank Darabont",
                'release_year': 1994
            },
           {
                'id': 2,
                'title': "The Godfather",
                'director': "Francis Ford Coppola",
                'release_year': 1972
            },
           {
                'id': 3,
                'title': "The Dark Knight",
                'director': "Christopher Nolan",
                'release_year': 2008
            },
           {
                'id': 4,
                'title': "Pulp Fiction",
                'director': "Quentin Tarantino",
                'release_year': 1994
            },
           {
                'id': 5,
                'title': "Schindler's List",
                'director': "Steven Spielberg",
                'release_year': 1993
            }
    ]
@app.route('/films/api/v1.0/movies', methods=['GET'])
def get_movies():
    return jsonify({'movies': movies})

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

Here is how this function looks when invoked from curl:

$ curl -i http://127.0.0.1:3000/films/api/v1.0/movies
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 07:33:33 GMT
Content-Type: application/json
Content-Length: 665
Connection: close

{
  "movies": [
    {
      "director": "Frank Darabont",
      "id": 1,
      "release_year": 1994,
      "title": "The Shawshank Redemption"
    },
    {
      "director": "Francis Ford Coppola",
      "id": 2,
      "release_year": 1972,
      "title": "The Godfather"
    },
    {
      "director": "Christopher Nolan",
      "id": 3,
      "release_year": 2008,
      "title": "The Dark Knight"
    },
    {
      "director": "Quentin Tarantino",
      "id": 4,
      "release_year": 1994,
      "title": "Pulp Fiction"
    },
    {
      "director": "Steven Spielberg",
      "id": 5,
      "release_year": 1993,
      "title": "Schindler's List"
    }
  ]
}

What if we make a request to the URI which does not point to any resource, by default the flask is made to return the 404 error in html format:

$ curl -i http://127.0.0.1:3000/films/api/v1.0/movies/4
HTTP/1.1 404 NOT FOUND
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 07:37:14 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 207
Connection: close

<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>

Since it is an API, we expect the response in JSON format. So let's overwrite the default:

#!/usr/bin/python3
from flask import Flask, jsonify, make_response
app = Flask(__name__)

movies = [
           {
                'id': 1,
                'title': "The Shawshank Redemption",
                'director': "Frank Darabont",
                'release_year': 1994
            },
           {
                'id': 2,
                'title': "The Godfather",
                'director': "Francis Ford Coppola",
                'release_year': 1972
            },
           {
                'id': 3,
                'title': "The Dark Knight",
                'director': "Christopher Nolan",
                'release_year': 2008
            },
           {
                'id': 4,
                'title': "Pulp Fiction",
                'director': "Quentin Tarantino",
                'release_year': 1994
            },
           {
                'id': 5,
                'title': "Schindler's List",
                'director': "Steven Spielberg",
                'release_year': 1993
            }
    ]
@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Resource Not Found'}), 404)

@app.route('/films/api/v1.0/movies', methods=['GET'])
def get_movies():
    return jsonify({'movies': movies})

if __name__ == '__main__':
    app.run(debug=True)
$ curl -i http://127.0.0.1:3000/films/api/v1.0/movies/4
HTTP/1.1 404 NOT FOUND
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 07:46:51 GMT
Content-Type: application/json
Content-Length: 36
Connection: close

{
  "error": "Resource Not Found"
}

Let's write a POST route for the creating another movies:

#!/usr/bin/python3
from flask import Flask, jsonify, make_response, request, abort
app = Flask(__name__)

movies = [
           {
                'id': 1,
                'title': "The Shawshank Redemption",
                'director': "Frank Darabont",
                'release_year': 1994
            },
           {
                'id': 2,
                'title': "The Godfather",
                'director': "Francis Ford Coppola",
                'release_year': 1972
            },
           {
                'id': 3,
                'title': "The Dark Knight",
                'director': "Christopher Nolan",
                'release_year': 2008
            },
           {
                'id': 4,
                'title': "Pulp Fiction",
                'director': "Quentin Tarantino",
                'release_year': 1994
            },
           {
                'id': 5,
                'title': "Schindler's List",
                'director': "Steven Spielberg",
                'release_year': 1993
            }
    ]
@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Resource Not Found'}), 404)

@app.route('/films/api/v1.0/movies', methods=['GET'])
def get_movies():
    return jsonify({'movies': movies})

@app.route('/films/api/v1.0/movies', methods=['POST'])
def create_movie():
    if not request.json or not 'title' in request.json:
        """If the data isn't there, or if it is there, but we are missing a title item
          then we return an error code 400, which is the code for the bad request."""
        abort(400)
    movie = {
        'id': movies[-1]['id'] + 1,
        'title': request.json['title'], # The request.json will have the request data, but only if it came marked as JSON
        'director': request.json['director'],
        'release_year': request.json['release_year']
    }
    movies.append(movie)
    return jsonify({'movie': movie}), 201

if __name__ == '__main__':
    app.run(debug=True)
$ curl -i -H "Content-Type: application/json" -X POST -d "{\"title\":\"StartUp\", \"director\":\"Ben Ketai\", \"release_year\":2016}" http://localhost:3000/films/api/v1.0/movies
HTTP/1.1 201 CREATED
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 08:14:05 GMT
Content-Type: application/json
Content-Length: 112
Connection: close

{
  "movie": {
    "director": "Ben Ketai",
    "id": 6,
    "release_year": 2016,
    "title": "StartUp"
  }
}

Now that you understand the logic, here is the complete program with all the HTTP verbs

#!/usr/bin/python3
from flask import Flask, jsonify, make_response, request, abort
app = Flask(__name__)

movies = [
           {
                'id': 1,
                'title': "The Shawshank Redemption",
                'director': "Frank Darabont",
                'release_year': 1994
            },
           {
                'id': 2,
                'title': "The Godfather",
                'director': "Francis Ford Coppola",
                'release_year': 1972
            },
           {
                'id': 3,
                'title': "The Dark Knight",
                'director': "Christopher Nolan",
                'release_year': 2008
            },
           {
                'id': 4,
                'title': "Pulp Fiction",
                'director': "Quentin Tarantino",
                'release_year': 1994
            },
           {
                'id': 5,
                'title': "Schindler's List",
                'director': "Steven Spielberg",
                'release_year': 1993
            }
    ]
@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Resource Not Found'}), 404)

@app.route('/films/api/v1.0/movies', methods=['GET'])
def get_movies():
    return jsonify({'movies': movies})

@app.route('/films/api/v1.0/movies/<int:movie_id>', methods=['GET'])
def get_movie(movie_id):
    movie = [movie for movie in movies if movie['id'] == movie_id]
    if len(movie) == 0:
        abort(404)
    return jsonify({'movie': movie[0]})

@app.route('/films/api/v1.0/movies', methods=['POST'])
def create_movie():
    if not request.json or not 'title' in request.json:
        """If the data isn't there, or if it is there, but we are missing a title item
          then we return an error code 400, which is the code for the bad request."""
        abort(400)
    movie = {
        'id': movies[-1]['id'] + 1,
        'title': request.json['title'], # The request.json will have the request data, but only if it came marked as JSON
        'director': request.json['director'],
        'release_year': request.json['release_year']
    }
    movies.append(movie)
    return jsonify({'movie': movie}), 201

@app.route('/films/api/v1.0/movies/<int:movie_id>', methods=['PUT'])
def update_movie(movie_id):
    movie = [movie for movie in movies if movie['id'] == movie_id]
    if len(movie) == 0:
        abort(404)
    if not request.json:
        abort(400)
    movie[0]['title'] = request.json.get('title', movie[0]['title'])
    movie[0]['director'] = request.json.get('director', movie[0]['director'])
    movie[0]['release_year'] = request.json.get('release_year', movie[0]['release_year'])
    return jsonify({'movie': movie[0]})

@app.route('/films/api/v1.0/movies/<int:movie_id>', methods=['DELETE'])
def delete_movie(movie_id):
    movie = [movie for movie in movies if movie['id'] == movie_id]
    if len(movie) == 0:
        abort(404)
    movies.remove(movie[0])
    return jsonify({'result': True})

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

Improving the web service interface

The current design of the API returns movie identifiers, which requires clients to construct URIs based on these identifiers. This could cause issues if URI structures change in the future.

  • Instead of returning movie IDs, the API will return the full URI that controls the task.

  • A helper function called make_public_task is defined. This function takes a task object and generates a new task object with all fields except the ID replaced with a uri field, which is generated using Flask's url_for function. The url_for function generates the URL for a given endpoint (here, get_movies) and its parameters.

  • Whenever the API returns a list of movies, it passes each task through this make_public_task function to convert it into a "public" version with URIs instead of IDs.

  • make_public_task function:

    • Iterates over each field in the task object.

    • If the field is 'id', it replaces it with a 'uri' field generated using url_for.

    • Otherwise, it copies the field as is.

  • get_movies endpoint:

    • Returns a JSON response containing the list of tasks after converting each task using the make_public_task function.
  • When a client requests the list of tasks, they receive JSON data where each task object contains a URI field instead of an ID field.

  • This ensures that clients always receive URIs and don't need to construct them based on IDs, making the API more robust to future changes in URI structures.

#!/usr/bin/python3
from flask import Flask, jsonify, make_response, request, abort, url_for
app = Flask(__name__)

movies = [
           {
                'id': 1,
                'title': "The Shawshank Redemption",
                'director': "Frank Darabont",
                'release_year': 1994
            },
           {
                'id': 2,
                'title': "The Godfather",
                'director': "Francis Ford Coppola",
                'release_year': 1972
            },
           {
                'id': 3,
                'title': "The Dark Knight",
                'director': "Christopher Nolan",
                'release_year': 2008
            },
           {
                'id': 4,
                'title': "Pulp Fiction",
                'director': "Quentin Tarantino",
                'release_year': 1994
            },
           {
                'id': 5,
                'title': "Schindler's List",
                'director': "Steven Spielberg",
                'release_year': 1993
            }
    ]

def make_public_movie(movie):
    new_movie = {}
    for field in movie:
        if field == 'id':
            new_movie['uri'] = url_for('get_movie', movie_id=movie['id'], _external=True)
        else:
            new_movie[field] = movie[field]
    return new_movie

@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Resource Not Found'}), 404)

@app.route('/films/api/v1.0/movies', methods=['GET'])
def get_movies():
    return jsonify({'movies': [make_public_movie(movie) for movie in movies]})

@app.route('/films/api/v1.0/movies/<int:movie_id>', methods=['GET'])
def get_movie(movie_id):
    movie = [movie for movie in movies if movie['id'] == movie_id]
    if len(movie) == 0:
        abort(404)
    return jsonify({'movie': make_public_movie(movie[0])})

@app.route('/films/api/v1.0/movies', methods=['POST'])
def create_movie():
    if not request.json or not 'title' in request.json:
        """If the data isn't there, or if it is there, but we are missing a title item
          then we return an error code 400, which is the code for the bad request."""
        abort(400)
    movie = {
        'id': movies[-1]['id'] + 1,
        'title': request.json['title'], # The request.json will have the request data, but only if it came marked as JSON
        'director': request.json['director'],
        'release_year': request.json['release_year']
    }
    movies.append(movie)
    return jsonify({'movie': make_public_movie(movie)}), 201

@app.route('/films/api/v1.0/movies/<int:movie_id>', methods=['PUT'])
def update_movie(movie_id):
    movie = [movie for movie in movies if movie['id'] == movie_id]
    if len(movie) == 0:
        abort(404)
    if not request.json:
        abort(400)
    movie[0]['title'] = request.json.get('title', movie[0]['title'])
    movie[0]['director'] = request.json.get('director', movie[0]['director'])
    movie[0]['release_year'] = request.json.get('release_year', movie[0]['release_year'])
    return jsonify({'movie': make_public_movie(movie[0])})

@app.route('/films/api/v1.0/movies/<int:movie_id>', methods=['DELETE'])
def delete_movie(movie_id):
    movie = [movie for movie in movies if movie['id'] == movie_id]
    if len(movie) == 0:
        abort(404)
    movies.remove(movie[0])
    return jsonify({'result': True})

if __name__ == '__main__':
    app.run(debug=True)
$ curl -i http://127.0.0.1:3000/films/api/v1.0/movies
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 08:45:31 GMT
Content-Type: application/json
Content-Length: 900
Connection: close

{
  "movies": [
    {
      "director": "Frank Darabont",
      "release_year": 1994,
      "title": "The Shawshank Redemption",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/1"
    },
    {
      "director": "Francis Ford Coppola",
      "release_year": 1972,
      "title": "The Godfather",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/2"
    },
    {
      "director": "Christopher Nolan",
      "release_year": 2008,
      "title": "The Dark Knight",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/3"
    },
    {
      "director": "Quentin Tarantino",
      "release_year": 1994,
      "title": "Pulp Fiction",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/4"
    },
    {
      "director": "Steven Spielberg",
      "release_year": 1993,
      "title": "Schindler's List",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/5"
    }
  ]
}

Securing a RESTful Web Service

Now that we've built the functionality of our API, it's time to ensure that only authorized users can access and manipulate the data. This is a critical aspect of maintaining data integrity and security in any application.

Statelessness and Authentication

Remember, our API is RESTful, which means it should be stateless. This implies that authentication mechanisms must be implemented in a way that doesn't rely on server-side sessions but instead utilizes client-side information in each request.

Using HTTP Authentication

HTTP protocol provides two standard authentication mechanisms: Basic and Digest. These mechanisms allow clients to authenticate themselves with each request.

Flask-HTTPAuth Extension

To implement authentication in our Flask application, we'll use the Flask-HTTPAuth extension. This extension simplifies the process of adding authentication to our endpoints.

Installation

To install Flask-HTTPAuth, execute the following command:

$ pip install flask-httpauth

Implementation

  1. Import the necessary module in your Flask application:
from flask_httpauth import HTTPBasicAuth
  1. Initialize the authentication object:
auth = HTTPBasicAuth()
  1. Define a function to authenticate users:
@auth.verify_password
def verify_password(username, password):
    if username == 'waltertaya' and password == 'password123':
        return True
    return False
  1. Apply the @auth.login_required decorator to the endpoints that require authentication:
@app.route('/films/api/v1.0/movies', methods=['GET'])
@auth.login_required
def get_movies():
    return jsonify({'movies': [make_public_movie(movie) for movie in movies]})

By incorporating Flask-HTTPAuth into our application, we ensure that only authenticated users can access our API endpoints. This enhances the security and integrity of our data, aligning with best practices for RESTful web services.

$ curl -i -u waltertaya:password123 http://127.0.0.1:3000/films/api/v1.0/movies
$ curl -i http://127.0.0.1:3000/films/api/v1.0/movies
HTTP/1.1 401 UNAUTHORIZED
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 09:32:28 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 19
WWW-Authenticate: Basic realm="Authentication Required"
Connection: close

Unauthorized Access
$ curl -i -u waltertaya:password123 http://127.0.0.1:3000/films/api/v1.0/movies
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Sat, 30 Mar 2024 09:33:16 GMT
Content-Type: application/json
Content-Length: 900
Connection: close

{
  "movies": [
    {
      "director": "Frank Darabont",
      "release_year": 1994,
      "title": "The Shawshank Redemption",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/1"
    },
    {
      "director": "Francis Ford Coppola",
      "release_year": 1972,
      "title": "The Godfather",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/2"
    },
    {
      "director": "Christopher Nolan",
      "release_year": 2008,
      "title": "The Dark Knight",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/3"
    },
    {
      "director": "Quentin Tarantino",
      "release_year": 1994,
      "title": "Pulp Fiction",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/4"
    },
    {
      "director": "Steven Spielberg",
      "release_year": 1993,
      "title": "Schindler's List",
      "uri": "http://127.0.0.1:3000/films/api/v1.0/movies/5"
    }
  ]
}

Access the full code