How to Create a Basic API with Waffleweb
Tags: webdev, python, api, tutorial
Published: 2022-08-08 05:02:24

In this tutorial we will be creating an API to give you debate topics. We will be building the API with Waffleweb.

For those who don't know Waffleweb is a Python web framework. It is lightweight and highly customizable. You can find Waffleweb at https://github.com/Berserkware/waffleweb.

## Prerequisites

To create the API you will need Python and Waffleweb installed. Since there are many tutorials on how to install Python available, You will only been shown how to install Waffleweb.

### Installing Waffleweb

Installing Waffleweb is easy with [pip](https://pip.pypa.io/en/stable/).

```
pip install waffleweb
```

## Setting Up the Project

To set up your project all you need to do is create a main.py file. The main.py file is the file that has all your routes. It also runs the webserver. For now it just need to have to following code.

`main.py:`
```python
from waffleweb import app
    
#Runs the built-in webserver.
if __name__ == '__main__':
    app.run()
```

## Creating the Basic Pages

The basic pages will educate your users on what the API does, and how to use it. To start creating the basic pages we first need to create some templates. 

### Creating the Files Needed

Let's start by creating a folder named "templates" and putting two files in it: "index.html" and "usage.html". We will put some data in these files. These pages are pretty basic, so customize as you please.

`templates/index.html:`
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Debate API</title>
</head>
<body>
    <h1>Welcome to the Debate API!</h1>
    
    <h2>Usage: <a href="/usage">Usage</a></h2>
    
    <h3>Debate of the Day:</h3>
    <h2>{{ debateOfTheDay }}</h2>
</body>
</html>
```

`templates/usage.html:`
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Debate API Usage</title>
</head>
<body>
    <h1>Debate API Usage</h1>
    <h2>/api/random</h2>
    <p>Gets a random debate topic.<br>Returns:</p>
    <code>
        {"topic": "[Insert Topic Here]", "id": id}
    </code>
    
    <h2>/api/{id}</h2>
    <p>Gets a debate topic by ID.<br>Returns:</p>
    <code>
        {"topic": "[Insert Topic Here]", "id": id}
    </code>
    
    <h2>/api/debate-of-the-day</h2>
    <p>Gets the debate topic of the day.<br>Returns:</p>
    <code>
        {"topic": "[Insert Topic Here]", "id": id}
    </code>
</body>
</html>
```

We also need to create a folder called "data" and add a file called "data.json". We also need to populate it with some boilerplate data. We will also add some debate questions. The debateOfTheDay should be the index of the debate of the day.

`data/data.json:`
```json
{
    "topics": [
        "Nuclear energy is the best energy.",
        "Python is the best programming language.",
        "Social media is unhealthy.",
        "Apples are the best fruit."
    ],
    "debateOfTheDay": 0
}
```

### Routing the Functions

Now we need to route the functions to return the page. We can do this easily by using the `route` decorator. The index page will have some extra logic to retrieve the debate of the day.

`main.py:`
```python
from waffleweb import app
from waffleweb.response import render
import json    

@app.route('/')
def index(request):
    with open('data/data.json', 'r') as f:
        #Gets the data and loads it into a dictionary
        data = json.loads(f.read())
        #This gets the id of the debate of the day then finds the matching topic
        debateOfTheDay = data['topics'][data['debateOfTheDay']]
        
    return render(request, 'index.html', {'debateOfTheDay': debateOfTheDay})
    
@app.route('/usage')
def usage(request):
    return render(request, 'usage.html')

#Runs the built-in webserver.
if __name__ == '__main__':
    app.run()
```

The only argument the `route` decorator is the URL to access that page. The `render` function returns a template.

You can now test the pages by running the "main.py" file.

## Creating the API

To create the API section of your site, all we need to do is route some functions. We will be routing three pages, one for a random topic, one for the debate of the day and one to get a topic by its ID.

### Routing the Functions

To route the function we will, again, use the `route` decorator.

`main.py:`
```python
from waffleweb import app
from waffleweb.response import render, JSONResponse
import json
import random
    
@app.route('/')
def index(request):
    with open('data/data.json', 'r') as f:
        #Gets the data and loads it into a dictionary
        data = json.loads(f.read())
        #This gets the id of the debate of the day then finds the matching topic
        debateOfTheDay = data['topics'][data['debateOfTheDay']]
        
    return render(request, 'index.html', {'debateOfTheDay': debateOfTheDay})
    
@app.route('/usage')
def usage(request):
    return render(request, 'usage.html')

@app.route('api/random')
def randomTopic(request):
    with open('data/data.json', 'r') as f:
        #Gets the data and loads it into a dictionary
        data = json.loads(f.read())
        
        topics = data["topics"]
        randomTopicID = random.randint(0, len(topics))
        
    return JSONResponse(request, {"topic": topics[randomTopicID], "id": randomTopicID})

@app.route('api/debate-of-the-day')
def debateOfTheDay(request):
    with open('data/data.json', 'r') as f:
        #Gets the data and loads it into a dictionary
        data = json.loads(f.read())
        
        #This gets the id of the debate of the day then finds the matching topic
        debateOfTheDayID = data['debateOfTheDay']
        debateOfTheDay = data['topics'][debateOfTheDayID]
        
    return JSONResponse(request, {"topic": debateOfTheDay, "id": debateOfTheDayID})

@app.route('api/<id:int>')
def getTopicByID(request, id):
    with open('data/data.json', 'r') as f:
        #Gets the data and loads it into a dictionary
        data = json.loads(f.read())
        
        topics = data["topics"]
        
    if type(id) != int:
        return JSONResponse(request, {"error": "The ID has to be an int."})
        
    if id < 0 or id >= len(topics):
        return JSONResponse(request, {"error": "A topic with that id does not exist."})
        
    return JSONResponse(request, {"topic": data["topics"][id], "id": id})

#Runs the built-in webserver.
if __name__ == '__main__':
    app.run()
```

In the route for getting a topic by id you might notice a strange bit of text in it: "`<id:int>`". This is an URL variable that allow us to have variable parts in your URLs. We can then access the variables as function arguments.

The `JSONResponse` is a response specifically for JSON.

## Conclusion

Thanks for reading this tutorial! I hope you had success following this tutorial. Feedback is much appreciated!