img

Fake API endpoints with Django REST mock

Published on March 8, 2018

updated 1 year, 8 months ago



Projects that are built upon an API backend and a SPA framework on the frontend are becoming more prevalent. Popular JS frameworks and libraries like Angular, React and Vue have taken the frontend world by storm in the past few years. For Django, the very popular Django REST Framework has kept many Django developers in the game as this trend grows.

However, developing such projects are sometimes not simple from many perspectives. Such projects typically require a lot of coordination from all parties, the API developers need to know exactly what the UI needs to consume, the UI team needs to fake a lot of data before the endpoints are ready. In an ideal world where all the information is readily available, the UI team can develop without waiting for the API developers to finish the endpoints. As long as they have the API specs ready, they should be able to create fixtures to mock the data used for the UI. However, it's also common that such information is sometimes not always available, or communication is not easy when teams are located in different locations. Even when the UI team connects to the backend, there might be times when the backend is unstable, or the responses are unpredictable.

From a backend perspective, it would be great if I could quick write up some views that mock the responses without actually writing the views, at the same time, document how the responses should look like. Wouldn't that be great?

Enter Django REST Mock...

This prompted me to create a tool - Django REST Mock - that could quickly generate a server based on the docstrings in your views without needing you to actually code your views. Here's a quick example of what I mean:

class SomeView(APIView):
    """
    URL: /api/someview/
    """
    def get(self, request, *args, **kwargs):
        """
        ```
        {
            "response": "Hello, world!"
        }
        ```
        """
        pass
Ok, so maybe I lied a bit about not writing the views...you do need to write a bit of the views but only the bare minimum part of declaring the class and the method. Then you can go ahead and specify the URL for that view and then specify the expected response.

Remember to add your view to URL conf! - The actual URL string doesn't matter, what matters is that the view is registered by Django.

Once you have this in place, all you need to do is run

$ python manage.py startmockserver

and this will start an ExpressJS server on port 8000, and if you visit localhost:8000/api/someview/ you'd see:

{"response": "Hello, world!"}

Great! We've just quickly spun up an endpoint that guarantees to return this response no matter when you visit it because these responses are hardcoded into the server. Say goodbye to the days where the frontend devs will scream at you for messing up the APIs, just throw them a generated server file and they can get to work as though they were working with a real server.

Dynamic Values
Of course, in a real project, there are often endpoints that return tons of data or dynamic data. We can generate a few RESTful endpoints by doing the following (let's say we wanted to be able to List, Create, Retrieve, Update, Delete users)

class UserListCreateView(ListCreateAPIView):
    """
    URL: /api/users/__key
    """
    def post(self, request, *args, **kwargs):
        """
        ```
        {
            "__options": {
                "excludeKey": true
            }
        }
        ```
        """
        pass

class UserRetrieveUpdateDestroyView(RetrieveUpdateDestroyAPIView):
    """
    URL: /api/users/__key
    """
    def get(self, request, *args, **kwargs):
        """
        ```
        {
            "__key": "<id:int>",
            "__key_position": "url",
            "__mockcount": 5,
            "__options": {
                "modifiers": ["patch", "put", "delete"],
                "excludeKey": false
            },
            "id": "<int>",
            "name": "<name>"
        }
        ```
        """
        pass

    def put(self, request, *args, **kwargs):
        """We don't need to specify anything here"""
        pass

    def patch(self, request, *args, **kwargs):
        """We don't need to specify anything here"""
        pass

    def delete(self, request, *args, **kwargs):
        """We don't need to specify anything here"""
        pass

AGAIN, remember to add the views to URL conf.

Woah, that might be quite a bit of stuff going on. Let's break it down slowly...

  • Focus on the second view class first
  • We first write a response as though we are retrieving only one user with id and name, this will generate a random integer and random name for these attributes
  • Notice the special keys with double-underscores, these are meta-keys that specify something special about the mock response
  • The first one __key is separated into two parts <where-to-find-the-key>:<what-is-the-data-type>, this means that we will be able to find the key from the user's id attribute and it should be an integer
  • The __key_position specifies whether the key will be in the url like above or as a query, for example /api/users?id[int]=__key
  • __mockcount specifies how many user instances we will create
  • The options must be specified to enable the other methods PUT, PATCH and DELETE.
  • We put excludeKey to true because we want the URL to be created with the __key, which will be replaced with the user's ID when the server file is created
  • Now let's look at the first class, we do nothing but specify that excludeKey is true for the POST method, because when we make a POST request to the URL we don't need to specify any user ID - since we're creating a new user

That's it! How the server stores the state is via a simple Javascript object which can be modified by the modifier methods, but once you restart the server it will go back to its original state.

There are some other interesting functions such as being able to specify references etc, which you can find in the README of the repository.

I hope this can help anyone else who might be facing similar issues to quickly create mock endpoints, while forcing yourselves to document exactly how your endpoints should look like even before you start coding :)