Week 6 · APIs and REST: OpenAPI and Flask-RESTful
The one-minute story
An API is a restaurant menu plus a waiter. You do not walk into the kitchen. You read the menu (the documentation), tell the waiter "one dosa, please" (an HTTP request), and the waiter brings the dish (the response) without showing you how it was cooked. A REST API is a menu with strict, simple manners: every dish has its own name (URL), every action has a standard word (GET to look, POST to order, PUT to change the order, DELETE to cancel), and the waiter treats every call as new: he does not remember your last visit, so you carry what you need each time (stateless).
A normal web page route returns HTML for a person. An API route returns JSON for another program: a mobile app, a Vue page, or a test script.
By the end of this week you can:
- explain what an API is and name the REST constraints;
- design URLs and choose the right verb and status code for each action;
- write a Flask-RESTful API with
Resource,reqparse,marshal_withandabort; - predict the output of Flask-RESTful code and of
curlcommands (including which call gives an error); - document an API in OpenAPI (Swagger) YAML;
- test an API with curl, Postman or Python
requests.
Words you need
- API - Application Programming Interface: a defined way for one program to talk to another.
- REST - a style for web APIs built on resources, URLs and standard HTTP verbs.
- Resource - a thing the API exposes (a student, a book), named by a URL.
- Endpoint - one URL plus one method (for example
GET /api/books/3). - JSON - JavaScript Object Notation: the text format for API data (objects, lists, strings, numbers, true, false, null).
- Stateless - the server keeps no memory of earlier requests; each request carries all it needs.
- Idempotent - doing it once or many times leaves the server in the same state.
- Status code - the three-digit result number (200, 404...).
- OpenAPI - a standard format (in YAML or JSON) to describe an API; Swagger is the tooling around it.
- Flask-RESTful - a Flask extension where each resource is a class with methods named after HTTP verbs.
- reqparse - Flask-RESTful's helper to read and validate request data.
- marshal / marshal_with - shape the response using a fields dictionary.
- curl - a command-line program that sends HTTP requests.
- Pagination - returning a long list in pages.
Idea 1: What an API is and what makes it REST
The server exposes data, and any client can use it: a browser page, an Android app, or a Python script. They agree on URLs, methods and JSON.
REST constraints (know each in one line):
- Client-server: UI and data storage are separate, so each can change on its own.
- Stateless: every request has everything the server needs (no server-side memory between calls).
- Cacheable: responses say whether they can be reused, so repeated reads are cheap.
- Uniform interface: resources identified by URLs, standard verbs (GET, POST, PUT, DELETE), representations such as JSON.
- Layered system: the client cannot tell if there is a proxy or load balancer between it and the server.
(A sixth, "code on demand", is optional.)
API route versus web page route. A web route returns HTML (render_template) and is meant for a person; an API route returns JSON and a status code, and its errors are JSON too (not a styled error page). In one real question the route / returned a Python dict and the answer was that its content type is application/json; and the route returning jsonify({"The list of gadgets": Gadgets}) was a valid API access point: both / and /home can be considered API endpoints.
JSON in 60 seconds
{
"name": "Asha",
"age": 27,
"married": false,
"phones": ["212-555-1234", 2125551234],
"address": {"city": "Chennai", "pin": null}
}
Rules: double quotes only on keys and strings, no trailing comma, no comments, values are string, number, true, false, null, object or array. Python mapping: object is dict, array is list, true is True, null is None. In JavaScript, JSON.parse('{"name":"icecream","flavor":"vanilla","type":"cup"}') gives an object, and obj.name + ", " + obj.flavor + ", " + obj.type prints icecream, vanilla, cup: access is by key name, not by position.
Idea 2: Designing the API: URLs, verbs, status codes
Name resources with nouns, in plural, and put the action in the verb, not in the URL.
| Action | Method and URL | Success status |
|---|---|---|
| list all books | GET /api/books | 200 |
| read one | GET /api/books/3 | 200 (404 if absent) |
| create | POST /api/books with a JSON body | 201 Created |
| replace/update | PUT /api/books/3 with a JSON body | 200 (or 204) |
| delete | DELETE /api/books/3 | 200 or 204 No Content |
Not RESTful: GET /api/deleteBook?id=3 or /getAllBooks. (A real exam question used URLs like /delete/val1 and a PUT request: because a route only answered PUT, a plain GET gave "none of these" = 405.)
The CRUD mapping: Create = POST, Read = GET, Update = PUT (or PATCH for a partial update), Delete = DELETE.
Safe and idempotent
| Method | Safe (no change) | Idempotent |
|---|---|---|
| GET | yes | yes |
| PUT | no | yes (setting the same value twice gives the same state) |
| DELETE | no | yes (the second delete changes nothing more; it may return 404) |
| POST | no | no (two POSTs create two records) |
Status codes for APIs
| Code | Meaning | Typical use |
|---|---|---|
| 200 | OK | successful GET or PUT |
| 201 | Created | successful POST that made a record |
| 204 | No Content | success with no body (delete) |
| 400 | Bad Request | malformed or missing input |
| 401 | Unauthorized | not logged in or bad credentials |
| 403 | Forbidden | logged in but not allowed |
| 404 | Not Found | no such resource or URL |
| 405 | Method Not Allowed | the URL exists but not for this verb |
| 409 | Conflict | duplicate, such as an existing username |
| 422 | Unprocessable | input is well-formed but fails validation |
| 500 | Server Error | a bug in your code |
Question. Match each action: (a) a new student is added; (b) a student's grade is changed; (c) a student that does not exist is requested; (d) someone adds a student whose roll number already exists; (e) a student is removed.
Solution. (a) POST /students, 201. (b) PUT /students/4, 200. (c) GET /students/99, 404. (d) POST /students, 409 Conflict (some courses use 400 or 422; say which you picked and why). (e) DELETE /students/4, 204 (or 200 with a message).
- Do not return 200 with
{"error": ...}for a failure. Use the proper code so clients can react. - 401 is "who are you?", 403 is "I know you, but no".
- A 404 for a missing resource and a 405 for a wrong verb are different problems.
- POST is not idempotent; PUT and DELETE are.
Idea 3: Flask-RESTful: Api, Resource and methods
Install: pip install flask-restful. Each Resource is a class; its methods are named get, post, put, delete and are called by the verb. You register it with add_resource(Class, url...).
from flask import Flask, request
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
students = {1: {"name": "Arjun", "grade": "A"}}
class Student(Resource):
def get(self, student_id):
if student_id in students:
return students[student_id], 200
return {"message": "Student not found"}, 404
def put(self, student_id):
data = request.json
if student_id in students:
students[student_id]["grade"] = data.get("grade", students[student_id]["grade"])
return {"message": "Student record updated", "student": students[student_id]}, 200
return {"message": "Student not found"}, 404
api.add_resource(Student, "/student/<int:student_id>")
I ran this with the test client. A GET /student/1 returns status 200 and {"name": "Arjun", "grade": "A"}. GET /student/10 returns 404 and {"message": "Student not found"}. PUT /student/1 with JSON {"grade": "A+"} returns 200 and the updated student (grade A+). PUT /student/4 returns 404 because 4 is not in the dictionary: it does not create a record. A URL like /student/abc does not match <int:...> and gives the plain 404 page.
What Flask-RESTful does for you: a returned dict or list becomes JSON; a returned tuple (body, status) sets the status; the response mimetype is application/json. Returning None gives JSON null with status 200.
Reading add_resource (very common question).
- The URL variables must match the method's parameters by name.
def get(self, tno)with"/api/ver1/<int:tno>"works;"/api/ver1/<tno>"gives a stringtno, sotrain["train_no"] == tno(an int) would never be equal. - One class can have many URLs:
api.add_resource(UserResource, "/users", "/users/<int:user_id>"). The same method is used for each; a method that has the wrong parameters for the URL fails with a 500 error. - A verb with no method in the class gives 405 Method Not Allowed.
- Only the
<...>parts bind variables./api/<brand>/<model>matches/api/maruti/swift, but not/api/brand=Ford,model=Fiesta(one piece, one variable missing) and not/api/Honda&City.
Question.
class UserResource(Resource):
def get(self):
return {"status": "GET request"}, 200
def post(self, user_id):
return {"status": "POST request", "user_id": user_id}, 201
api.add_resource(UserResource, "/users", "/users/<int:user_id>")
Which of these give an error: GET /users, GET /users/5001, POST /users, POST /users/5001?
Solution. I ran it. GET /users gives 200. POST /users/5001 gives 201 and {"status": "POST request", "user_id": 5001}. GET /users/5001 fails: Flask passes user_id=5001 to get(self), which takes no such argument: TypeError ... unexpected keyword argument, shown as 500. POST /users fails the same way: post(self, user_id) is called with nothing: TypeError ... missing 1 required positional argument. The two failing commands are GET /users/5001 and POST /users.
Question.
class MyApi(Resource):
def get(self): return {"greet": "Hello from GET Api!"}
def put(self): return {"greet": "Hello from PUT Api!"}
api.add_resource(MyApi, "/api/get", "/api/put", "/api/post")
Which of curl .../api/get -X get, -X put on /api/put, -X post on /api/post, -X put on /api/get, -X get on /api/put and -X get on /api/post throw an error?
Solution. The URL name means nothing: all three URLs go to the same class, whose methods are get and put. Only the verb matters. So everything works except POST (no post method), which gives 405: only -X post on /api/post fails. A lower-case -X get also works, because Flask-RESTful lower-cases the method name.
Idea 4: Input and output: reqparse, marshal_with, abort
reqparse declares which inputs you expect, converts their type and rejects bad input with a 400.
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument("n", type=int, required=True, help="n must be an int")
class MyResponse(Resource):
def post(self):
args = parser.parse_args()
return args # a dict-like, so it becomes JSON
api.add_resource(MyResponse, "/")
curl -X POST http://127.0.0.1:5000 -H "Content-Type: application/json" -d "{\"n\":3}"
The reply is {"n": 3} (status 200). With {"n": "x"}, or with no n at all, the reply is 400 and {"message": {"n": "n must be an int"}}. If you send JSON with -d but leave out the Content-Type header, Flask 3 refuses it with a 415 error: so for JSON, curl needs -H "Content-Type: application/json" (as the real question's right answers had). Plain -d "status=active&role=admin" sends form-encoded data (not JSON); with -X PUT it is a PUT in form format, which was the right answer to a real question.
By default reqparse looks in the JSON body and the form. For values from the query string, say so: parser.add_argument("page", type=int, default=1, location="args"). (I found that on Flask 3 a GET with no JSON header and reqparse's default location raises 415, so always set location="args" for GET parameters.)
marshal_with filters and converts your output to the shape in a fields dictionary.
from flask_restful import fields, marshal_with
out_fields = {"Id": fields.Integer, "name": fields.String}
class MyApi(Resource):
@marshal_with(out_fields)
def post(self):
info = parser.parse_args() # parser has Id, name, email
return info
curl -X POST ... -d '{"Id":3, "name":"user_1", "email":"user_1@gmail.com"}' returns {"Id": 3, "name": "user_1"}. The email is dropped because it is not in the fields dictionary, and "Id": 3 is a number (fields.Integer), not the string "3". A real question had the same code and its right option was {"Id": 3, "name": "user_1"}. The get method in that class returned a plain dict with the keys Identity, Name, E-mail: no marshal there, so keys are what you wrote.
marshal_with(fields, envelope="book") wraps the output as {"book": {...}}. Fields: fields.Integer, fields.String, fields.Float, fields.Boolean, fields.List(...), fields.Nested(...).
abort with a message. Flask-RESTful has its own abort.
from flask_restful import abort
abort(404, message="Book 99 not found") # JSON: {"message": "Book 99 not found"}, status 404
Question. Write the full API for books: list with paging, read one, create with validation.
Solution. Tested code:
from flask import Flask
from flask_restful import Api, Resource, reqparse, fields, marshal_with, marshal, abort
app = Flask(__name__)
api = Api(app)
ITEMS = [{"id": i, "title": f"Book {i}", "price": i * 10, "secret": "x"} for i in range(1, 6)]
book_fields = {"id": fields.Integer, "title": fields.String, "price": fields.Float}
list_parser = reqparse.RequestParser()
list_parser.add_argument("page", type=int, default=1, location="args")
list_parser.add_argument("per_page", type=int, default=2, location="args")
list_parser.add_argument("q", location="args")
new_parser = reqparse.RequestParser()
new_parser.add_argument("title", required=True, help="title is required")
new_parser.add_argument("price", type=int, default=0)
class Books(Resource):
def get(self):
a = list_parser.parse_args()
rows = [b for b in ITEMS if not a["q"] or a["q"] in b["title"]]
start = (a["page"] - 1) * a["per_page"]
return {"page": a["page"], "total": len(rows),
"items": [marshal(b, book_fields) for b in rows[start:start + a["per_page"]]]}
@marshal_with(book_fields, envelope="book")
def post(self):
a = new_parser.parse_args()
b = {"id": len(ITEMS) + 1, "title": a["title"], "price": a["price"]}
ITEMS.append(b)
return b, 201
class Book(Resource):
@marshal_with(book_fields)
def get(self, bid):
for b in ITEMS:
if b["id"] == bid:
return b
abort(404, message=f"Book {bid} not found")
api.add_resource(Books, "/api/v1/books")
api.add_resource(Book, "/api/v1/books/<int:bid>")
Results from a test client:
GET /api/v1/booksgives 200 and{"page": 1, "total": 5, "items": [{"id": 1, "title": "Book 1", "price": 10.0}, {"id": 2, ...}]}. Thesecretfield never appears:marshaldropped it.priceis10.0becausefields.Floatis used.GET /api/v1/books?page=3&per_page=2gives the last page with one item (id 5).GET /api/v1/books?q=Book 4givestotal1.POST /api/v1/bookswith{"title": "New", "price": 55}gives 201 and{"book": {"id": 6, "title": "New", "price": 55.0}}; withouttitleit gives 400 and{"message": {"title": "title is required"}}.GET /api/v1/books/99gives 404 and{"message": "Book 99 not found"}.
The /api/v1/ in the path is versioning: when the design must change in a way that breaks clients, you publish /api/v2/ and let the old one live on.
salaries = {1: {"name": "Ravi", "basic": 14500}, 2: {"name": "David", "basic": 9500}}
class Salaries(Resource):
def do_something(self):
for s in salaries.keys():
salaries[s]["hra"] = salaries[s]["basic"] * 0.10
def get(self):
self.do_something()
return salaries
api.add_resource(Salaries, "/api/salaries")
What does GET /api/salaries return?
Need a hint?
do_something changes the original dictionary.
Show the full solution
Each entry keeps its name and basic and gains hra: {"1": {"name": "Ravi", "basic": 14500, "hra": 1450.0}, "2": {"name": "David", "basic": 9500, "hra": 950.0}}. JSON keys are always strings, so the integer keys 1 and 2 print as "1" and "2". do_something is an ordinary helper method, not an HTTP verb.
Two resources: api.add_resource(Default, "/") (its get(self) returns {"output": "Default computed value : 0"}) and api.add_resource(Power, "/<int:x>/<int:y>") (its get(self, x, y) returns {"output": "Computed x^y = " + str(x**y)}). What do GET /, GET /6/2, GET /x=6/y=2 and GET /6 give?
Need a hint?
Count the pieces, and remember each variable is only the value.
Show the full solution
/ gives the default message. /6/2 gives {"output": "Computed x^y = 36"}. /x=6/y=2 is Not Found: x=6 is not an integer, so <int:x> does not match. /6 is Not Found too: only one piece, two are needed. (I ran this.)
Idea 5: Calling and testing an API
curl options to know:
| Option | Meaning |
|---|---|
-X POST | choose the HTTP method (GET is the default) |
-H "Content-Type: application/json" | add a header |
-d '{"name":"Asha"}' | request body (implies POST if -X is absent) |
-v | verbose: shows request and response headers (for debugging) |
-i | print the response headers followed by the body |
curl http://127.0.0.1:5000/api/v1/books/1
curl -X POST http://127.0.0.1:5000/api/v1/books -H "Content-Type: application/json" -d "{\"title\":\"New\",\"price\":55}"
curl -v http://127.0.0.1:5000/api/v1/books
curl -X DELETE http://127.0.0.1:5000/api/v1/books/1
On Windows cmd, JSON in -d must use double quotes with \" escapes (as in line 2); in Git Bash or Linux you can use single quotes. A real question asked which option shows detailed request/response information for debugging: curl -v (-i just adds the response headers to the output).
From Python with the requests library:
import requests
r = requests.post("http://127.0.0.1:5000/api/v1/books", json={"title": "New", "price": 55})
print(r.status_code) # 201
print(r.json()) # {'book': {...}}
r = requests.get("http://127.0.0.1:5000/api/v1/books", params={"page": 2})
json= sets the body and the Content-Type header for you. Postman is a point-and-click tool for the same: choose a method, enter the URL, put JSON in the Body tab (raw, JSON), and read status and body.
Write a command that updates student 1's grade to B using the Student resource above.
Need a hint?
The verb is PUT, the body is JSON, and the header says so.
Show the full solution
curl -X PUT http://127.0.0.1:5000/student/1 -H "Content-Type: application/json" -d "{\"grade\":\"B\"}"
Expected: 200 and {"message": "Student record updated", "student": {"name": "Arjun", "grade": "B"}}. Without the header, Flask 3 answers 415.
Expose at least one JSON API for your project's main table (CRUD for a resource), with proper status codes and JSON errors. Keep the web page routes (HTML) separate from the API routes (/api/...). In the viva, you may be asked to call it with curl, so keep two or three working commands ready, and the OpenAPI file next to it.
Idea 6: Documenting with OpenAPI (Swagger)
Documentation says what endpoints exist, their parameters, body and responses. In the exam, "what describes available endpoints, request parameters and response formats?" has the answer API documentation / API reference; and "OpenAPI Specification is a way to describe interfaces for RESTful APIs" (it is not a programming language, performance tester or auth library).
An OpenAPI file is YAML (or JSON) with these top-level keys:
| Key | Purpose |
|---|---|
openapi | the version of the spec (for example 3.0.3) |
info | title, version, description |
servers | base URLs |
paths | each URL, then each method under it |
components/schemas | reusable data shapes, referenced with $ref |
openapi: 3.0.3
info:
title: Books API
version: "1.0"
paths:
/api/v1/books:
get:
summary: List books
parameters:
- name: page
in: query
schema: {type: integer, default: 1}
responses:
"200":
description: A page of books
content:
application/json:
schema:
type: array
items: {$ref: "#/components/schemas/Book"}
post:
summary: Create a book
requestBody:
required: true
content:
application/json:
schema: {$ref: "#/components/schemas/NewBook"}
responses:
"201": {description: Created}
"400": {description: Missing or invalid title}
/api/v1/books/{bid}:
get:
summary: Get one book
parameters:
- name: bid
in: path
required: true
schema: {type: integer}
responses:
"200": {description: The book}
"404": {description: No such book}
components:
schemas:
Book:
type: object
properties:
id: {type: integer}
title: {type: string}
price: {type: number}
NewBook:
type: object
required: [title]
properties:
title: {type: string}
price: {type: integer}
Points to notice: path variables are written {bid} (curly braces) here, but <int:bid> in Flask; parameters have in: (query, path, header); response codes are strings; the YAML is indentation-sensitive (use spaces, never tabs). Swagger UI or the Swagger Editor reads this file and gives you a page where you can read and try the API.
Idea 7: Pagination, filtering, versioning and error responses
- Pagination:
GET /books?page=2&per_page=10. The server returns one slice and often the total, so clients do not download everything. - Filtering and sorting:
GET /books?author=Asha&sort=price. Filters go in the query string, resource identity goes in the path. - Versioning: put the version in the path (
/api/v1/) or a header, so old clients keep working. - Error responses: always JSON with the right status, such as
{"message": "Book 99 not found"}(404). Flask-RESTful does this for you when you use itsabort. - Cacheable GET: GET responses can be cached; POST responses normally not.
(a) PUT is idempotent. (b) A REST server stores each client's state between requests. (c) abort(404, message="...") from flask_restful returns JSON. (d) -d '{...}' alone is enough for a JSON POST in Flask 3.
Need a hint?
Recall the constraints and what Flask 3 does.
Show the full solution
(a) True. (b) False: REST is stateless. (c) True. (d) False: you need -H "Content-Type: application/json"; otherwise 415.
Exam radar
The evidence counts questions whose text matches week 6 topics (keyword match, approximate): about 24 of 692 in Quiz 1, about 82 of 624 in Quiz 2 and about 108 of 1600 in End Term, with 56 distinct questions in our sample. Many Quiz 2 items are "read this Flask-RESTful class and a URL or curl command, then say the output".
What the sample asks:
- Which curl command or URL works / throws an error, depending on methods defined,
add_resourceURLs and URL variables (about 6 questions in the sample). - Predict the JSON a resource returns (including
marshal_withdropping fields and converting types, and thereqparseresult{"n": 3}). - Status codes and mimetype of a
jsonifyresponse (200 andapplication/json). - Match GET, PUT, POST with their URLs; read a PUT that updates a database row and print the final state.
- Theory: what OpenAPI is, what documentation contains, valid JSON,
curl -v, form-encoded versus JSON.
- A URL variable like
<int:x>means the URL has just the value,/6/2;x=6/y=2in the path is Not Found. - Only the method decides which Python method runs; the URL text (such as
/api/post) means nothing. curlwith-d '{...}'but noContent-Type: application/jsonheader is not a JSON request.marshal_withremoves keys that are not in the fields dictionary.- JSON keys are always strings, so integer dictionary keys print with quotes.
- A method whose parameters do not match the URL variables is a runtime (500) error, not a 404.
Quick quiz
Which HTTP method is NOT idempotent?
- GET
- PUT
- DELETE
- POST
A resource class defines only get and put. A client sends POST to its URL. The response is...
- 404 Not Found
- 405 Method Not Allowed
- 200 with an empty body
- 201 Created
Which status code fits "a new record was created"?
- 200
- 201
- 204
- 301
A resource uses @marshal_with({"Id": fields.Integer, "name": fields.String}) and returns {"Id": "3", "name": "u", "email": "e@x.in"}. The JSON is...
{"Id": "3", "name": "u", "email": "e@x.in"}{"Id": 3, "name": "u"}{"name": "u", "email": "e@x.in"}- an error
Select all statements that are TRUE about REST. (select all that apply)
- It is stateless.
- Resources are identified by URLs.
- The server must remember the last request of every client.
- It can only return HTML.
In an OpenAPI file, which top-level section lists the URLs and their methods?
- info
- paths
- components only
- servers
paths holds each URL and the operations under it; info is title and version.Viva: say it out loud
What is a REST API?
It is an API on top of HTTP in which each thing (a resource) has a URL and we use the standard verbs on it: GET to read, POST to create, PUT to update and DELETE to remove. It is stateless, so each request carries what the server needs, and it normally exchanges JSON.
Why is REST called stateless?
The server does not keep client information between calls. Each request has the data (and any token) needed to process it. That makes it easy to scale, because any server can handle any request.
Which status codes does your API return and when?
200 for successful reads and updates, 201 after creating a record, 204 after delete, 400 for bad input, 401 or 403 for authentication and permission problems, 404 when the resource does not exist, 409 for duplicates and 500 only for my own bugs.
Difference between reqparse and marshal_with?
reqparse handles input: it reads, converts and validates the request arguments and returns a 400 if they are wrong. marshal_with handles output: it decides which fields appear in the JSON and converts their types.
How is an API route different from a normal Flask route?
A normal route returns HTML for a browser, using a template. An API route returns JSON with a status code, errors are in JSON, and it is used by a program such as a Vue page or a script. In my project the API routes start with /api/.
How did you document and test your API?
I wrote an OpenAPI YAML file with info, paths, parameters, request bodies, responses and component schemas, and I tested every endpoint with curl and Postman. I can show a POST with a JSON body and the 201 response.
Why POST for create and PUT for update?
POST sends data to a collection and the server creates a new item each time, so repeating it is not safe. PUT sets the state of one known item, so repeating it gives the same result: it is idempotent.
What is versioning and why use it?
It means putting a version such as /api/v1/ in the URL. If I must change the response format later, I add v2 and the old clients still work on v1.
Recap
- An API is a contract between programs; REST is resources plus URLs plus standard verbs, with client-server, stateless, cacheable, uniform interface and layered constraints.
- CRUD maps to POST, GET, PUT, DELETE. GET is safe and idempotent; PUT and DELETE are idempotent; POST is not.
- Status codes: 200, 201, 204, 400, 401, 403, 404, 405, 409, 422, 500.
- Flask-RESTful:
Api(app),Resourcesubclasses withget/post/put/delete,add_resource(Class, url, ...); method arguments must match URL variables. reqparsevalidates input (setlocation="args"for query values);marshal_withshapes output;abort(code, message=...)sends a JSON error.- JSON needs double quotes; curl needs
-H "Content-Type: application/json"for a JSON body;-vshows headers. - OpenAPI YAML:
openapi,info,paths(parameters,requestBody,responses),components/schemas. - Pagination and filtering use the query string; version the API in the path.
api = Api(app);api.add_resource(Cls, "/x", "/x/<int:id>")class Cls(Resource): def get(self, id): return {...}, 200parser = reqparse.RequestParser(); parser.add_argument("n", type=int, required=True, location="json");args = parser.parse_args()@marshal_with({"id": fields.Integer, "name": fields.String})abort(404, message="...")fromflask_restfulcurl -X POST URL -H "Content-Type: application/json" -d "{\"a\":1}";curl -v URLrequests.post(URL, json={...}),.status_code,.json()- 201 created, 204 no content, 400 bad input, 401 not logged in, 403 not allowed, 404 missing, 405 wrong verb, 409 conflict
- OpenAPI:
openapi: 3.0.3,info,paths: /x: get: parameters/responses,components: schemas
- MAD2 week 5 - Vue with APIs: fetch, axios and lifecycle: The REST API you built in MAD1 week 6 is exactly what Vue consumes in MAD2 week 5.
- MAD2 week 8 - Token authentication, API design, CORS and cookies: API design rules (status codes, resources, errors) carry over; token authentication is added on top.
Practise with real exam questions
These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week All Quiz 2 papers