DEV Community

Cover image for How I Wrote My First Web API
Varun S
Varun S

Posted on • Originally published at varuns.hashnode.dev

How I Wrote My First Web API

As part of my Borum Jot project, I made a Web API for all my front-end platforms to create, retrieve, update, and delete (CRUD) data on the database. In this article, I'll discuss what exactly an API is and how I made my own Web API.

There are two main kinds of APIs.

One is a software and its documentation, and this is used very liberally. For example, the old JavaScript library jQuery has a documentation so developers know how to use it. This documentation web site is referred to as the jQuery API. Other examples are documentations for programming languages, frameworks, or even ecosystems. It is usually specified in the URL, even if it's not directly on the page.

Thanks to @mankms on Twitter, I learned that a Web API refers to sets of pages to which clients make network requests, and they return back data, possibly querying a database, in a specific structure. I like to think of a Web API as part of a filtration system:

API as a filtration system

This concept is called information hiding, and it is important for simplicity and security. But before I could begin hiding information, I had to obtain information. I began by choosing a language.

Choosing a Language and Content Type

I used PHP, a back-end language, for my API. A back-end language sits on the server and accesses data.

I used JSON, or JavaScript Object Notation, for the content type. It is used to represent data in nested objects. In my API, I sent JSON that looked like this:

{
    "statusCode": 200,
    "data": [
        {
            "id": "10",
            "title": "Go to Grocery Store",
            "body": "Go to this grocery store",
            "user_id": "115",
            "completed": "1",
            "time_updated": "1038829292",
            "parent_id": "0",
            "priority": "0",
            "source": "task"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Once the client receives the above response and parses the JSON, it can retrieve the value of any property, such as statusCode.

Host Configuration and File Structure

I now knew I was writing in PHP to make a Web API that sends JSON to a client (the front-end). Surprisingly, where I hosted was very important at this point. I deployed on Vercel, a hosting platform. Although Vercel is designed for JavaScript and React-type projects, the vercel-php runtime allows developers to host backend PHP projects on a secure domain (as opposed to directly on the domain registrar, which may require configuring GitHub, paying for an SSL certificate, etc.).

Because I was deploying on Vercel, I needed to store all the endpoints in the api directory and configure the now.json file. The now.json file tells vercel-php which files were serverless functions. These serverless functions served as the API endpoints that sent the response data in JSON format.

Vercel Function Logs

Vercel Function Logs

In addition, I've seen APIs specify their version (e.g. v1). So far, this was my file structure (note the composer.json is for including PHP libraries)

api/
    v1/
       ?
now.json
composer.json
Enter fullscreen mode Exit fullscreen mode

What would go inside the v1 subdirectory?

Writing my First Endpoint

Finally, I could begin writing the code (the fun part!).

In its simplest form, a Web API is one PHP file that returns the JSON, such as one of the examples above, on every request.

<?php 

header("HTTP/1.1 200 OK");
header("Content-Type: application/json; charset=UTF-8");

echo json_encode(["english" => "Hello world!", "french" => "Bonjour!", "german" => "Guten tag!"];

?>
Enter fullscreen mode Exit fullscreen mode

Notice the header function calls, establishing the server response headers. The 200 OK is an HTTP Status Code that tells the client that everything ran okay and it can now access the data. The json_encode turns a PHP Associative Array into JSON format. The API deploys through Vercel and has the following as its now.json file:

{
  "functions": {
    "api/v1/*.php": {
      "runtime": "vercel-php@0.3.1"
    },
  },
  "routes": [ 
   { "src": "/v1/greeting", "dest": "/v1/greeting.php" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The functions object specifies the directories that have API endpoints and will always have the same runtime property. The routes array contains paths at the location of dest that rewrite the url to src. In the above example, /v1/greeting.php simply becomes /v1/greeting.


Helper Classes and Added Complexity

The second level of complexity was to interact with the database. I created a separate folder called include and put it in the api directory in case I wanted to have a v2. Remember, putting everything in one file still makes it an API, but I put database handling in a separate class to make my code DRY and modular. When I have more than one response object (such as a GET and POST request to the same endpoint, or multiple endpoints), I could call code I had already written. My modified file structure looked like this:

api/
    include/
       Config.php
       SimpleRest.php
       DBHandler.php  
    v1/
       greeting.php
now.json
composer.json
Enter fullscreen mode Exit fullscreen mode

I copied the SimpleRest code from another site, expanding to fit to my API.

For every response from this point, I had only to call SimpleRest::setHttpHeaders(200) or another status code, replacing multiple header() calls with one method call of my own class.

Database Interaction

First and foremost is a Config.php file for database interaction. I used the mysqli_connect() call to connect with my database credentials to the MySQL database I host on GoDaddy. Next, I stored the PHP Connection object inside an instance variable named $conn in the DBHandler class in the constructor.

For every database-querying function, I wrote an instance method in the DBHandler class. Once my database, app, and API got larger, I expanded this into its own folder and namespace, but for now, I could keep everything in one class.

For instance, I wrote the createNewUser() method for a POST request to the register.php endpoint. Below is the code of this method.

public function createNewUser($firstname, $lastname, $email, $password) {
        if ($this->newUserValid($email)) {
            $query = "INSERT INTO firstborumdatabase.users 
                (first_name, last_name, email, pass, registration_date) 
                VALUES ('$firstname', '$lastname', '$email', SHA2('$password', 512), NOW())
            ";
            $newBorumUserResult = $this->executeQuery($query);
            if (mysqli_affected_rows($dbc) == 1) { # Query ran okay
                $accountId = $this->executeQuery("SELECT id FROM firstborumdatabase.users ORDER BY registration_date DESC LIMIT 1");
                $accountId = mysqli_fetch_array($accountId, MYSQLI_BOTH);
                $apikey = $this->generateApiKey();

                // If the generated api key is taken, keep generating until a unique one is found
                while ($this->apiKeyExistsInDatabase($apikey) != true) {
                    $apikey = $this->generateApiKey();
                }

                // Insert the newly created Borum user into the Borum Jot `users` table
                $newBorumJotUserResult = $this->executeQuery("INSERT INTO users (borum_user_id, time_created, api_key) VALUES ($accountId, NOW(), '$apikey')");
                if (mysqli_affected_rows($dbc) == 1) { # Query ran okay
                    return [
                        "ok" => true,
                        "statusCode" => 200
                    ];
                }
            }             
            return [
                "error" => [
                    "message" => "The user could not be validated at this time"
                ],
                "statusCode" => 500
            ];
        } else {
            return [
                "error" => [
                    "message" => "User with that email already exists on Borum"
                ],
                "statusCode" => 500
            ];
        }
    }
Enter fullscreen mode Exit fullscreen mode

And in the endpoint file, I would call it and my helper methods inside of one case of a switch statement that checks the request method:

case 'POST':
        SimpleRest::handlePostParameterValidation("name");

        $newnoteres = $handler->createNote($_POST['name']);

        SimpleRest::setHttpHeaders($newnoteres["statusCode"]);
        echo json_encode($newnoteres);
break;
Enter fullscreen mode Exit fullscreen mode

The $handler variable instantiates a new DBHandler object (or a subclass of that). I put a statusCode property in each JSON response so I could easily set the status code header.

And that's what I did for every new response that I needed to create - I made a new method that queried the database in a DBHandler class. If you know how to use PHP with MySQL, and you followed everything described above, you're ready to make your own Web API!

Don't I have to document it? What about unit tests? Stay tuned for Part 2, where I'll be covering all of this and more!

Top comments (0)