Philip Guo (Phil Guo, Philip J. Guo, Philip Jia Guo, pgbovine)

REST Web APIs: A Super-Simple Tutorial

Summary
I explain the basic idea behind REST Web APIs using two simple examples of non-REST APIs followed by a corresponding REST API.

Here is my attempt to explain REST APIs (otherwise known as REST Web services, REST Web APIs, or RESTful Web APIs) in a simple, jargon-free way.

Here's the simplest definition in my mind: REST is a style guide for Web APIs.

Say you're building a Web-based API for a super-simple version of Facebook. Here are the CRUD operations you want to support:

  • Create a new user profile by entering their name, job, and pet.

  • Read the profile of the user with a given name.

  • Update the profile of the user with a given name, such as entering a new job or pet for them.

  • Delete a given user.

How would you design this API?

Version 1: the simplest non-REST API

Let's start with the simplest-possible design, which doesn't follow REST principles. Create a single script (let's say in Python) that the user interacts with via HTTP POST requests. Let's call it doStuff.py and host it on the Facebook server at the following URL (obviously this is a fake URL, but play along):

http://facebook.com/doStuff.py

To use your API to do CRUD, the caller issues HTTP POST requests, either from an HTML form or from their own script.

Create

To create a new Facebook user, the caller issues the following POST request:

POST http://facebook.com/doStuff.py
Body: operation=create&name=Philip&job=professor&pet=cat

Your doStuff.py Python script parses the parameters in the POST body, sees that the requested operation is create, creates a new user with name Philip, job professor, and pet cat, and then inserts that new user's data into the database. So far so good.

Read

To read the user Philip's profile, issue the following POST request:

POST http://facebook.com/doStuff.py
Body: operation=read&name=Philip

Now your doStuff.py script parses the parameters, sees that the requested operation is read, reads the database record for the name Philip, and returns the following JSON data to the caller:

{"name": "Philip", "job": "professor", "pet": "cat"}

Update

To update Philip's job, once again POST to that same URL with slightly different parameters:

POST http://facebook.com/doStuff.py
Body: operation=update&name=Philip&job=cat_herder

Now Philip has turned into a cat herder.

Delete

Finally, to delete Philip from the database, use:

POST http://facebook.com/doStuff.py
Body: operation=delete&name=Philip

Version 1 recap

This simple API will work, but what are its shortcomings?

  • We've overloaded a single URL http://facebook.com/doStuff.py to perform four different actions. This is like creating an API with only a single “function” called doStuff() that takes different actions based on its parameters. It's not elegant.

  • Web infrastructure will not cache any requests since they're all POSTs. The read operation could be cached but isn't. (If we had used GET instead, then that would be unsafe since non-read operations shouldn't be cached.)

Version 2: a better but still non-REST API

OK now let's split our API into four separate Python scripts, each implementing one part of CRUD.

Create

To create a new Facebook user, the caller issues the following POST request:

POST http://facebook.com/createUser.py
Body: name=Philip&job=professor&pet=cat

The createUser.py script parses the parameters in the POST body, creates a new user with name Philip, job professor, and pet cat, and then inserts that new user's data into the database.

Unlike doStuff.py in Version 1, the caller no longer needs to pass in an operation=create parameter to createUser.py, since its only job is to create new users. That simplifies the parameter list.

Read

To read the user Philip's profile, issue the following GET request (which is now cacheable):

GET http://facebook.com/readUser.py?name=Philip

readUser.py parses its parameters from the URL, reads the database record for the name Philip, and returns JSON data:

{"name": "Philip", "job": "professor", "pet": "cat"}

Update

To update Philip's job, POST to the following URL:

POST http://facebook.com/updateUser.py
Body: name=Philip&job=cat_herder

Now Philip has (again) turned into a cat herder.

Delete

Finally, to delete Philip from the database, use:

POST http://facebook.com/deleteUser.py
Body: name=Philip

Version 2 recap

This version improves upon Version 1 since there are now dedicated scripts for each CRUD operation. The URLs now look like function names, which resemble verbs:

http://facebook.com/createUser.py http://facebook.com/readUser.py http://facebook.com/updateUser.py http://facebook.com/deleteUser.py

This API will work perfectly fine, but it somehow doesn't feel very “Web-like.” Why not?

When we normally think of the Web, we don't think of each URL as referring to a verb, but rather to a noun. For instance, the following URL refers to an HTML file (this webpage!):

http://pgbovine.net/rest-web-api-basics.htm

And this URL refers to an image file:

http://pgbovine.net/cs243-teaching.jpg

HTML files, image files, video files, and other Web resources are all nouns, not verbs. So how can we make our simple Facebook API look like it's operating on nouns, to make it more consistent with existing Web conventions? That's where REST comes in.

Version 3: Finally a REST API

Here is a simple REST API for our four CRUD operations:

Create

To create a new Facebook user, issue a POST request to the following URL:

POST http://facebook.com/users/
Body: name=Philip&job=professor&pet=cat

Whoa, what's going on here?!? It looks like users/ is simply a directory name, judging by its URL. That's exactly what we want it to look like. Conceptually, users/ is a “directory” containing a collection of all Facebook users.

But how can we POST to a directory? Actually, visiting that URL runs a script behind the scenes. A Web application framework handles this magic mapping between URLs and scripts so that any URL can run an arbitrary script.

Translated into English, the above request reads like “POST a new resource to the users/ collection at http://facebook.com/ with name=Philip, job=professor, and pet=cat.” In other words, create a new user in the users/ collection.

Read

To read Philip's profile, issue the following GET request:

GET http://facebook.com/users/Philip

Whoa, what's going on? Translated into English, this request reads like “GET the Philip resource in the users/ collection at http://facebook.com.” In other words, it's requesting the profile data for the user Philip. The right script magically runs and then returns the following JSON data to the caller:

{"name": "Philip", "job": "professor", "pet": "cat"}

Since we are using a plain GET request, it looks like we're simply requesting a resource named Philip in the same way that we would request an HTML file. But the magic here is that a script actually runs to dynamically generate JSON data for the caller.

Update

To update Philip's job, issue the PUT request to the following URL:

PUT http://facebook.com/users/Philip
Body: job=cat_herder

Now Philip has (yet again) turned into a cat herder.

Translated into English, this request reads like “PUT new data job=cat_herder into the Philip resource in the users/ collection.”

(For simplicity, HTTP POST can also work here instead of PUT.)

Delete

Finally, to delete Philip from the database, issue the (rarely-used) HTTP DELETE request:

DELETE http://facebook.com/users/Philip

Translated into English, this request reads like “DELETE the Philip resource from the users/ collection.”

Version 3 REST API recap

Compared to the non-REST API (Version 2), this REST API feels more concise and elegant, as though the caller is visiting Web resources (nouns) rather than making function calls. Note that the verbs actually come from the HTTP request types themselves (e.g., POST, GET, PUT, and DELETE), which operate on the nouns (URLs). Also, the hierarchical structure of noun-based REST URLs more closely matches your database's schema than the verb-based URLs of Version 2.

One final way to think about the difference is that Version 2 (non-REST) is like a function-oriented (procedural) API, whereas Version 3 (REST) is like an object-oriented API.

In the end, both APIs can accomplish the same tasks. REST is merely a style guide for a convention that is now popular across the Web. It's ultimately up to you whether to follow it or not.

Non-REST versus REST API

To highlight the style contrast, here are the CRUD operations in both Version 2 and 3 APIs, along with the colloquial English translations for Version 3 (REST).

Create

  • Non-REST:

    POST http://facebook.com/createUser.py
    Body: name=Philip&job=professor&pet=cat

  • REST:

    POST http://facebook.com/users/
    Body: name=Philip&job=professor&pet=cat

    English translation: “Create (POST) a new resource in the users/ collection with the given parameters.”

Read

  • Non-REST:

    GET http://facebook.com/readUser.py?name=Philip

  • REST:

    GET http://facebook.com/users/Philip

    English translation: “GET the Philip resource from the users/ collection.”

Update

  • Non-REST:

    POST http://facebook.com/updateUser.py
    Body: name=Philip&job=cat_herder

  • REST:

    PUT http://facebook.com/users/Philip
    Body: job=cat_herder

    “PUT new data job=cat_herder into the Philip resource.”

Delete

  • Non-REST:

    POST http://facebook.com/deleteUser.py
    Body: name=Philip

  • REST:

    DELETE http://facebook.com/users/Philip

    English translation: “DELETE the Philip resource from the users/ collection.”


Appendix: Code example

Here is a simple Node.js web app that implements the principles in this article. Specifically, server.js implements the backend with a REST API, and fakebook.html implements the frontend.

Created: 2015-10-12
Last modified: 2015-10-12
Related pages tagged as programming: