Using curl or httpie to Test All the Endpoints
In this section, we will go through ways to test the API service endpoints in our recipe management application using Command Prompt. Testing is a very important step in application development. This is to ensure the functions we developed are working as expected. We can use curl or httpie, depending on your personal preference. In the subsequent exercise, we will show you both tools.
Curl (or cURL) is a command-line tool that can transfer data using URLs. We can use this tool to send requests to our API endpoints and examine the response. If you are running on macOS, you don't need to install curl. It is pre-installed in the system and you can find it in Terminal. You can also run it in the Terminal in PyCharm. However, if you are running on Windows, you need to download and install it for free from http://curl.haxx.se/download.html.
Httpie (aych-tee-tee-pie) is another command-line client that does a similar thing. It was built with the goal to improve the communication between the CLI (command-line interface) and the web. It is pretty user-friendly. For more details about httpie, please refer to https://httpie.org/.
We added httpie==1.0.2
in our requirements.txt previously, so PyCharm should have already installed it for us. The main benefit of having httpie is it will beautifully format the JSON document, making it more readable. And believe me, that will save us a lot of time when we move on to verifying the HTTP response from the server.
Exercise 3: Testing Our API Endpoints with httpie and curl
In this exercise, we are going to use httpie and curl to test our API endpoints. We will test the functions of getting all the recipes back from the server, and also creating/updating the recipes:
- We will first open the Terminal in PyCharm. It is located at the bottom of the application. It will look as shown in the following screenshot:
Figure 1.9: PyCharm Terminal
- Type in the following httpie command to get the recipes from our API endpoint,
http://localhost:5000/recipes
; we will be using the HTTP GET method here:http GET localhost:5000/recipes
- If you prefer to do it the curl way, use the following command instead. Note that we have different parameters here:
-i
is for showing the header in the response and-X
is for specifying the HTTP method. We will be usingGET
here:curl -i -X GET localhost:5000/recipes
Note
The http GET and curl-i -X GET commands basically do the same thing, which is using the HTTP
GET
method to send a request tohttp://localhost:5000/recipes
. If the code that we put in on the server-side is working properly, the request will go through the/recipes
route and theget_recipes
function will be invoked. This will then get us all the recipes in JSON format.Take a look at the response we get. The first few lines in the response are the header. It has the HTTP status
200 OK
and aContent-Length
of175
bytes. TheContent-Type
isapplication/json
and, in the end, we have the response body in JSON format:HTTP/1.0 200 OK Content-Length: 175 Content-Type: application/json Date: Mon, 15 Jul 2019 12:40:44 GMT Server: Werkzeug/0.15.4 Python/3.7.0 { "data": [ { "description": "This is a lovely egg salad recipe.", "id": 1, "name": "Egg Salad" }, { "description": "This is a lovely tomato pasta recipe.", "id": 2, "name": "Tomato Pasta" } ] }
- After that, let's create a recipe. This time, use the HTTP
POST
method, as we have lots of information that cannot be encoded in the URL. Please take a look at the following httpie command:http POST localhost:5000/recipes name="Cheese Pizza" description="This is a lovely cheese pizza"
- And then following is the curl command. The -H here is to specify the header in the request. Put in
Content-Type: application/json
, as we are going to send over the details of the new recipe in JSON format. The-d
here is to specify the HTTPPOST
data, which is our new recipe:curl -i -X POST localhost:5000/recipes -H "Content-Type: application/json" -d '{"name":"Cheese Pizza", "description":"This is a lovely cheese pizza"}'
- The
@app.route('/recipes', methods=['POST'])
in the backend to catch this client request and invoke thecreate_recipe
function. It will get the recipe details from the client request and save it to a list in the application memory. Once the recipe is successfully stored in the memory, it will return an HTTP status of201 CREATED
, and the new recipe will also be returned in the HTTP response for us to verify:HTTP/1.0 201 CREATED Content-Length: 77 Content-Type: application/json Date: Mon, 15 Jul 2019 14:26:11 GMT Server: Werkzeug/0.15.4 Python/3.7.0 { "description": "This is a lovely cheese pizza", "id": 3, "name": "Cheese Pizza" }
- Now, get all the recipes again to verify if our previous recipe was really created successfully. We expect to receive three recipes in the response now:
http GET localhost:5000/recipes curl -i -X GET localhost:5000/recipes
- Use either one of the preceding commands. They do the same thing, which is to trigger the
get_recipes
function and get us all the recipes currently stored in the application memory in JSON format.In the following response, we can see that the HTTP header is saying OK, and the Content-Length is now slightly longer than our previous response, that is,
252
bytes. This makes sense because we are expecting to see one more recipe in the response. The Content-Type is againapplication/json
, with the body storing the recipes in JSON format. Now we can see our new recipe with ID3
:HTTP/1.0 200 OK Content-Length: 252 Content-Type: application/json Date: Tue, 16 Jul 2019 01:55:30 GMT Server: Werkzeug/0.15.4 Python/3.7.0 { "data": [ { "description": "This is a lovely egg salad recipe.", "id": 1, "name": "Egg Salad" }, { "description": "This is a lovely tomato pasta recipe.", "id": 2, "name": "Tomato Pasta" }, { "description": "This is a lovely cheese pizza", "id": 3, "name": "Cheese Pizza" } ] }
- Cool! So far, we are in pretty good shape. Now, test our application by trying to modify the recipe with ID 3. Use the HTTP
PUT
method and send over the modified name and description of the recipe tolocalhost:5000/recipes/3
:http PUT localhost:5000/recipes/3 name="Lovely Cheese Pizza" description="This is a lovely cheese pizza recipe."
The following is the curl command. Again,
-H
is to specify the header in the HTTP request, and we are setting that to"Content-Type: application/json"
;-d
is to specify that our data should be in JSON format:curl -i -X PUT localhost:5000/recipes/3 -H "Content-Type: application/json" -d '{"name":"Lovely Cheese Pizza", "description":"This is a lovely cheese pizza recipe."}'
- If things are working properly, then the client request will be caught by the
@app.route('/recipes/<int:recipe_id>', methods=['PUT'])
route. It will then invoke theupdate_recipe(recipe_id)
function to look for the recipe with the passed-inrecipe_id
, update it, and return it. Together with the updated recipe in JSON format, we will also receive the HTTP status ofOK
(200
):HTTP/1.0 200 OK Content-Length: 92 Content-Type: application/json Date: Tue, 16 Jul 2019 02:04:57 GMT Server: Werkzeug/0.15.4 Python/3.7.0 { "description": "This is a lovely cheese pizza recipe.", "id": 3, "name": "Lovely Cheese Pizza" }
- Alright, all good so far. Now, go on and see if we can get a particular recipe. To do this, send a request to
localhost:5000/recipes/3
to get the recipe with ID3
, and confirm whether our previous update was successful:http GET localhost:5000/recipes/3
We can also use a
curl
command:curl -i -X GET localhost:5000/recipes/3
- The application will look for the recipe with the
recipe_id
and return it in JSON format, together with an HTTP status of200 OK
:HTTP/1.0 200 OK Content-Length: 92 Content-Type: application/json Date: Tue, 16 Jul 2019 06:10:49 GMT Server: Werkzeug/0.15.4 Python/3.7.0 { "description": "This is a lovely cheese pizza recipe.", "id": 3, "name": "Lovely Cheese Pizza" }
- Now, what if we try a recipe ID that we know doesn't exist? How will the application behave? Test it out with the httpie command as follows:
http GET localhost:5000/recipes/101
Alternatively, use the following
curl
command, which will do the same thing as in the preceding code:curl -i -X GET localhost:5000/recipes/101
- Similarly,
@app.route('/recipes/<int:recipe_id>', methods=['GET'])
in the application will catch this client request and try to look for the recipe with ID = 101. The application will return with an HTTP status of 404 and amessage: "recipe not found"
in JSON format:HTTP/1.0 404 NOT FOUND Content-Length: 31 Content-Type: application/json Date: Tue, 16 Jul 2019 06:15:31 GMT Server: Werkzeug/0.15.4 Python/3.7.0 { "message": "recipe not found" }
If your application passed the test, congratulations! It is a pretty solid implementation. You can choose to perform more tests by yourself if you want to.