AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

API Design: Building RESTful Services with Python Flask

📚 Computer Science⏱️ 23 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: September 2026 CBSE-aligned · Peer-reviewed · 23 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Two Seconds on the IRCTC App

It is eleven at night, the evening before a journey, and you open the IRCTC (Indian Railway Catering and Tourism Corporation) app to check whether your waitlisted ticket has confirmed. You type in your ten-digit PNR (Passenger Name Record) number and tap search. Less than two seconds later, the screen updates: "Confirmed, Coach S4, Seat 34, Lower Berth." Nothing about your phone changed in those two seconds — it did not suddenly gain a copy of Indian Railways' entire reservation database. What actually happened is that your phone sent a small, carefully formatted message across the internet to a computer owned by IRCTC, asking one specific question: what is the status of this exact PNR number? That distant computer looked up the answer in its database and sent back a reply, formatted just as carefully, which your app then arranged into the neat little card you saw on screen. This request-and-reply exchange between two programs, following an agreed-upon set of rules, is what programmers call an API call. By the end of this chapter, you will have built one yourself, from scratch, in Python.

Clients, Servers, and the Request-Response Cycle

Every API rests on a relationship between two programs: a client, which asks for something, and a server, which holds the something and answers. The IRCTC app on your phone is a client. The computer in an IRCTC data centre that stores the seat charts is a server. This pattern is everywhere, not just in railways: when your browser loads a news website, the browser is the client and the website's computer is the server; when a UPI app checks whether a payment went through, the app is the client and the bank's system is the server.

Clients and servers do not talk in plain English. They talk using HTTP (HyperText Transfer Protocol), a shared set of rules that lets a message sent by one program be understood correctly by the other, no matter who built either one. An HTTP request always carries a few essential parts: a method that names the kind of action being requested (such as GET or POST), a URL that names which resource the request concerns, and often a body carrying data. The response coming back carries a status code that summarises what happened in three digits, and usually a body containing the actual data. Once you can read a request and a response this way, an API stops looking like magic and starts looking like a conversation with a fixed grammar.

What an API Actually Is

Think about ordering food at your school canteen. You do not walk into the kitchen and help yourself to a plate of samosas. You go to the counter, tell the person there what you want, and they bring it out to you. The counter is a boundary: it exposes exactly what you are allowed to ask for — today's menu items — without exposing how the kitchen actually works: which stove is free, how the chutney is made, or where the ingredients are kept. An API (Application Programming Interface) plays exactly this role between two pieces of software. It is a defined set of requests one program is allowed to make of another, and the responses it can expect in return, without either program needing to know how the other is built on the inside. The canteen counter does not care whether the kitchen behind it is run by one person with a single stove or a large catering team, as long as the counter's menu and rules stay the same, you can order food the same way either time. Likewise, IRCTC could rewrite its entire database system tomorrow, and as long as it keeps answering PNR-status requests the same way, your app would never notice the difference. That stability — a fixed way of asking, regardless of what changes behind the scenes — is the entire point of designing an API well.

What Makes an API "RESTful"

There are several ways to design an API, but the style used by the overwhelming majority of web APIs today, including, almost certainly, the one behind your IRCTC app, is called REST (Representational State Transfer). The term was introduced in the year 2000 by the computer scientist Roy Fielding, one of the authors of the HTTP specification itself, in his doctoral dissertation at the University of California, Irvine. An API built along REST's rules is called RESTful. Four ideas matter most for a student building a first one:

  • Everything is a resource. A REST API is organised around nouns, not verbs: "a menu item," "a student record," "a train," not "getMenuItem" or "fetchStudent." Each resource gets its own address.
  • Resources are identified by URLs. The canteen's whole menu might live at /menu, and one specific item, say the one with ID 3, at /menu/3. Anyone who learns the pattern can guess how to reach related data.
  • A small set of HTTP methods does all the work. Instead of inventing a new instruction for every action, REST reuses standard HTTP methods, chiefly GET, POST, PUT, and DELETE, and applies them consistently to every resource.
  • The server keeps no memory of past requests. This is called statelessness. Every request must carry everything the server needs to handle it; there is no assumption that the server remembers what you asked five seconds earlier. If a PNR-status request does not include the PNR number, the server has no way of guessing which ticket is meant, even if the exact same PNR was checked a minute earlier. Statelessness is what lets a company like IRCTC add more servers behind the scenes during a peak booking period, say the days around Diwali or the start of summer vacation, without any single server needing to track which client it spoke to last. Any server can answer any request, because every request is self-contained.

HTTP Methods: The Verbs of REST

If resources are the nouns of a REST API, HTTP methods are its verbs. Four methods cover almost everything an API needs to do, and they map onto a pattern programmers call CRUD, short for Create, Read, Update, Delete:

  • GET — read a resource, without changing anything. Checking PNR status is a GET. Viewing the canteen menu is a GET.
  • POST — create a new resource. Booking a fresh ticket is a POST. Adding a new dish to the canteen menu is a POST.
  • PUT — update an existing resource. Changing the price of a menu item is a PUT.
  • DELETE — remove a resource. Taking a sold-out item off the menu is a DELETE.

Notice something useful here: GET, PUT, and DELETE are idempotent, meaning that repeating the same request several times in a row leaves the server in the same state as doing it just once. Deleting menu item 4 twice ends with item 4 gone either way; the second attempt simply has nothing left to remove. POST behaves differently: sending the same "add a new dish" request twice creates two separate dishes, because each POST is treated as a fresh instruction to create something new. Keeping this distinction straight is one of the things that separates a carefully designed API from a sloppy one.

Status Codes: How the Server Reports Back

Every HTTP response carries a status code that tells the client, at a glance, how the request went, without the client having to read and interpret a full sentence. The ones you will use constantly are:

  • 200 OK — the request succeeded, and here is the data you asked for.
  • 201 Created — a new resource was successfully created, the typical response to a successful POST.
  • 400 Bad Request — the request itself was malformed, so the server could not even understand what was being asked.
  • 404 Not Found — the server understood the request perfectly, but the resource being asked about does not exist.
  • 500 Internal Server Error — something broke on the server's side while it was trying to help.

When the IRCTC app shows an error instead of a PNR status, it is very likely reacting to a status code like 400 or 404 that arrived alongside, or instead of, a data body. The app is written to read that number and decide what message to show.

JSON: The Shared Format for Data

Knowing the method and the status code still leaves one question: what does the actual data look like as it travels between client and server? Almost every modern REST API answers this with JSON (JavaScript Object Notation), a lightweight, human-readable text format built from key-value pairs. A single canteen menu item, written as JSON, looks like this:

{
  "id": 2,
  "name": "Masala Dosa",
  "price": 40,
  "available": true
}

JSON uses curly braces for an object, double-quoted text for keys and text values, plain digits for numbers, and true or false for booleans. It looks similar to a Python dictionary on purpose, which is exactly why Python programs can convert between the two so easily, as you are about to see.

Meet Flask

Flask is a micro-framework for building web applications and APIs in Python. "Micro" does not mean limited; it means Flask gives you a small, clear core, mainly routing requests to functions and building responses, and otherwise stays out of your way, rather than forcing a rigid project structure on you. This makes it an excellent framework for learning how APIs actually work, since very little machinery is hidden from view. Flask is installed like any other Python package, from a terminal:

pip install flask

The smallest possible Flask application looks like this:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Welcome to the AICI Canteen API"

if __name__ == "__main__":
    app.run(debug=True)

Three things are worth naming here. app = Flask(__name__) creates the application object that will handle every incoming request. @app.route("/") is a decorator, a line starting with @ that attaches extra behaviour to the function written just below it, and it tells Flask: whenever a request arrives for the URL /, run the function directly beneath me. This pairing of a function to a URL is called a route. Running this file with python app.py starts a local web server, reachable by default at http://127.0.0.1:5000/, and visiting that address in a browser sends a GET request that Flask routes to home(), which returns the text shown on screen. debug=True is useful while building an API, since it restarts the server automatically whenever you save a change and shows detailed error pages, but it must never be left on when an API is actually deployed for real users, because it can expose an interactive debugger that would let anyone who finds it run code on your server.

Building the AICI Canteen API

Now for the complete system: a REST API that lets the school canteen's menu be viewed, added to, updated, and trimmed down, in much the same way a real ordering app would work behind the scenes.

from flask import Flask, jsonify, request

app = Flask(__name__)

menu = [
    {"id": 1, "name": "Samosa", "price": 15, "available": True},
    {"id": 2, "name": "Masala Dosa", "price": 40, "available": True},
    {"id": 3, "name": "Cold Coffee", "price": 30, "available": True}
]

@app.route("/menu", methods=["GET"])
def get_menu():
    return jsonify(menu), 200

@app.route("/menu/<int:item_id>", methods=["GET"])
def get_menu_item(item_id):
    for item in menu:
        if item["id"] == item_id:
            return jsonify(item), 200
    return jsonify({"error": "Item not found"}), 404

@app.route("/menu", methods=["POST"])
def add_menu_item():
    new_item = request.get_json()
    new_item["id"] = menu[-1]["id"] + 1 if menu else 1
    menu.append(new_item)
    return jsonify(new_item), 201

@app.route("/menu/<int:item_id>", methods=["PUT"])
def update_menu_item(item_id):
    for item in menu:
        if item["id"] == item_id:
            item.update(request.get_json())
            return jsonify(item), 200
    return jsonify({"error": "Item not found"}), 404

@app.route("/menu/<int:item_id>", methods=["DELETE"])
def delete_menu_item(item_id):
    for item in menu:
        if item["id"] == item_id:
            menu.remove(item)
            return jsonify({"message": "Item removed"}), 200
    return jsonify({"error": "Item not found"}), 404

if __name__ == "__main__":
    app.run(debug=True)

Notice the pattern repeating across every route: a URL that names a resource, /menu for the whole collection, /menu/<int:item_id> for one specific item, an HTTP method that names the action, and a Python function that carries it out. <int:item_id> is a Flask URL converter: it matches a segment of the incoming URL, checks that it is a whole number, converts it from text to an int, and hands it to the function as the argument item_id, so a request to /menu/2 calls get_menu_item(2) automatically, with no manual parsing required. jsonify() takes a Python list or dictionary and converts it into a properly formatted JSON response. request.get_json() does the reverse: it reads the JSON body sent by the client and turns it back into a Python dictionary your code can work with directly.

One precise distinction is worth knowing before you rely on PUT elsewhere. The HTTP specification defines PUT as replacing a resource's entire representation, while a closely related method called PATCH is meant for partial updates to just a few fields. Many practical APIs, including the one above, use PUT a little more loosely, to mean "update whatever fields are sent," which is why update_menu_item uses item.update(...) rather than rebuilding the item from nothing. Knowing the strict definition means you will reach for PATCH instead, on the day a project actually calls for it.

Another detail worth noticing sits inside add_menu_item(): the new ID is calculated as menu[-1]["id"] + 1, one more than the last item currently in the list. Because items are always appended to the end and never reordered, the last item in the list is always the one with the highest ID, so this line never accidentally creates two items sharing an ID. That is a fine approach for a teaching example with data stored in memory. A real canteen ordering system would instead store the menu in a proper database, which can generate unique IDs on its own and, unlike a plain Python list, will not forget the entire menu the moment the server restarts.

Tracing a Request, Step by Step

Reading the code is one thing; watching it actually execute is what makes it click. Suppose the server above is running, and a GET request arrives for /menu/2.

  1. Flask's router looks at the incoming method, GET, and path, /menu/2, and checks them against every registered route. /menu/<int:item_id> with methods=["GET"] matches, with item_id converted to the integer 2.
  2. get_menu_item(2) runs. Its loop checks each dictionary in menu in order: item["id"] == 2 is False for Samosa, whose id is 1, then True for Masala Dosa, whose id is 2.
  3. The loop returns immediately with jsonify({"id": 2, "name": "Masala Dosa", "price": 40, "available": True}), 200.
  4. The client receives a response carrying status code 200 and a JSON body containing exactly that dictionary, now formatted as JSON text.

Now trace something with more moving parts: a client adding a new item by sending a POST request to /menu carrying this JSON body:

{"name": "Vada Pav", "price": 20, "available": true}
  1. Flask matches POST /menu to add_menu_item(), since that route is registered for the POST method.
  2. Inside the function, request.get_json() parses the incoming body into a Python dictionary: {"name": "Vada Pav", "price": 20, "available": True}. This dictionary has no "id" key yet; the client is not expected to invent one.
  3. menu[-1]["id"] looks at the last item in the current list, Cold Coffee, whose id is 3, so new_item["id"] = 3 + 1 sets the new id to 4.
  4. menu.append(new_item) adds this fourth dictionary to the end of the list. The in-memory menu now holds four items instead of three.
  5. jsonify(new_item) converts the completed dictionary, now including its new id, into a JSON response body: {"id": 4, "name": "Vada Pav", "price": 20, "available": true}.
  6. The accompanying 201 attaches status code 201 Created to the response, correctly signalling to the client that a new resource now exists.

If the same client sent that identical POST request a second time, the trace would repeat from step 1, and the server, having no memory of the previous request, true to REST's statelessness, would create a fifth item with id 5, so "Vada Pav" would appear twice in the menu. This is exactly the non-idempotent behaviour of POST described earlier, now visible in running code rather than only in a definition.

Testing the API

Once app.py is running, the terminal shows that the server is listening, typically at http://127.0.0.1:5000. A GET route can be tested straight from a browser, since browsers send GET requests by default; simply visiting http://127.0.0.1:5000/menu displays the JSON list of menu items. POST, PUT, and DELETE requests need a tool that can set the method and attach a JSON body, such as the command-line tool curl:

curl -X POST http://127.0.0.1:5000/menu \
  -H "Content-Type: application/json" \
  -d '{"name": "Vada Pav", "price": 20, "available": true}'

The -X POST flag sets the HTTP method, -H attaches a header telling the server that the body is JSON, and -d supplies the JSON body itself. Graphical tools such as Postman offer the same capability through a form instead of a command line, which some learners find easier when starting out. Either approach exercises exactly the same API underneath.

What Separates a Well-Designed REST API from a Careless One

Writing routes that work is only the first step. Writing routes that other developers can pick up and use correctly, without reading your source code line by line, is what API design really means. A few habits make the difference:

  • Name resources with nouns, and let methods carry the actions. /menu with a POST is REST; /addMenuItem is not, because it smuggles a verb into the URL where a method should be doing that job.
  • Return the status code that actually matches what happened. A "not found" case answered with 200 and an error message buried inside the body forces every client to parse text just to know whether something went wrong, defeating the entire purpose of having status codes.
  • Keep responses consistent in shape. If /menu returns a list of objects, /menu/2 should return one object built the same way, not a differently structured response that client code has to special-case.
  • Never assume the server remembers anything between requests. The moment an API design relies on "the server already knows which user this is" without that information being resent, statelessness is broken, and scaling the API across multiple servers becomes far harder, precisely the problem a system at IRCTC's scale has to avoid during a Tatkal booking rush, when a large burst of requests must be shared across many servers at once.

Back to the PNR Check

Return to that ten-digit PNR number on the IRCTC app. You now know, with real precision, what those two seconds contained: a client sending an HTTP GET request to a URL representing one specific resource, your ticket, over a connection that assumes nothing from earlier requests; a server matching that request to the right internal logic, in essentially the same way Flask's router matched /menu/2 to get_menu_item; a lookup against a database; and a response carrying a status code and a JSON-shaped body back to your screen. The system running behind IRCTC is, of course, built for a scale and reliability far beyond a school project, handling millions of passengers across a network of thousands of trains. But the underlying grammar is the one you just wrote and tested yourself: a resource, a method, a route, and a response with the right status code. The next time an app on your phone answers you in under two seconds, you will know exactly what filled that gap. It was never magic. It was an API, most likely a RESTful one, doing precisely what you just built.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind api design: building restful services with python flask, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← HTTPS and SSL/TLS: Secure CommunicationDatabase Fundamentals: SQL for Data-Driven Applications →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn