├── requirements.txt
├── templates
├── index.html
└── result.html
├── nj_server.py
├── LICENSE.txt
├── ny_client.py
├── nj_advanced_server.py
├── static
└── style.css
└── README.md
/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi
2 | uvicorn
3 | requests
4 | jinja2
5 | python-multipart
6 |
7 |
8 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | API Buddy
5 |
6 |
7 |
8 |
9 |
API Buddy
10 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/nj_server.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | catalog = {
4 | "tomatoes": {
5 | "units": "boxes",
6 | "qty": 1000
7 | },
8 | "wine": {
9 | "units": "bottles",
10 | "qty": 500
11 | }
12 | }
13 |
14 | app = FastAPI( title = "New Jersey API Server")
15 |
16 | @app.get("/warehouse/{product}")
17 | async def load_truck(product, order_qty):
18 |
19 | available = catalog[product]["qty"]
20 |
21 | if int(order_qty) > int(available):
22 | from fastapi import HTTPException
23 | raise HTTPException(
24 | status_code=400,
25 | detail=f"Sorry, only {available} units are available, please try again…"
26 | )
27 |
28 | catalog[product]["qty"] -= int(order_qty)
29 |
30 | return {
31 | "product": product,
32 | "order_qty": order_qty,
33 | "units": catalog[product]["units"],
34 | "remaining_qty": catalog[product]["qty"]
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 MariyaSha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ny_client.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, Form
2 | from fastapi.responses import HTMLResponse
3 | from fastapi.templating import Jinja2Templates
4 | from fastapi.requests import Request
5 | from fastapi.staticfiles import StaticFiles
6 | import requests
7 | import nj_server
8 |
9 | ITEM_NAMES = list(nj_server.catalog.keys())
10 |
11 | API_URL = "http://localhost:8000/warehouse"
12 |
13 | app = FastAPI(title="API Buddy")
14 | app.mount(
15 | "/static",
16 | StaticFiles(directory="static"),
17 | name="static"
18 | )
19 |
20 | templates = Jinja2Templates(directory="templates")
21 |
22 | @app.get("/", response_class=HTMLResponse)
23 | def form(request: Request):
24 | return templates.TemplateResponse(
25 | "index.html",
26 | {
27 | "request": request,
28 | "products": ITEM_NAMES
29 | }
30 | )
31 |
32 | @app.post("/", response_class=HTMLResponse)
33 | def send(
34 | request: Request,
35 | product: str = Form(...),
36 | order_qty: int = Form(...)
37 | ):
38 | r = requests.get(
39 | f"{API_URL}/{product}",
40 | params={"order_qty": order_qty}
41 | )
42 | data = r.json()
43 |
44 | return templates.TemplateResponse(
45 | "result.html",
46 | {"request": request, "result": data}
47 | )
48 |
--------------------------------------------------------------------------------
/nj_advanced_server.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, HTTPException
2 | from pydantic import BaseModel
3 |
4 | # create data model
5 | class Product(BaseModel):
6 | units: str
7 | qty: int
8 |
9 | # create NJ warehouse catalog
10 | catalog = {
11 | "tomatoes": Product(
12 | units="boxes",
13 | qty=1000
14 | ),
15 | "wine": Product(
16 | units="bottles",
17 | qty=500
18 | )
19 | }
20 |
21 | # create API server
22 | app = FastAPI(title = "New Jersey API Server")
23 |
24 | # set up an endpoint for all catalog products
25 | @app.get("/warehouse/{product}")
26 | async def load_truck(product, order_qty):
27 | """
28 | deduct ordered product quantity from the catalog,
29 | updating the inventory.
30 | """
31 | # the available product quantity
32 | available = catalog[product].qty
33 |
34 | # if order quantity is greater than the quantity at hand
35 | if int(order_qty) > int(available):
36 | # don't process the order - raise an exception
37 | raise HTTPException(
38 | status_code=400,
39 | detail=f"Sorry, only {available} units are available, please try again…"
40 | )
41 | # otherwise - process order, and subtract order quantity from the inventory
42 | catalog[product].qty -= int(order_qty)
43 |
44 | # produce shipping confirmation
45 | return {
46 | "product": product,
47 | "order_qty": order_qty,
48 | "units": catalog[product].units,
49 | "remaining_qty": catalog[product].qty
50 | }
51 |
--------------------------------------------------------------------------------
/templates/result.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | API Buddy - Result
5 |
6 |
7 |
8 |
9 |
Result
10 |
11 | {% if error %}
12 |
❌ {{ error }}
13 |
14 |
Back
15 | {% else %}
16 | {% set label_map = {
17 | 'product': 'product',
18 | 'order_qty': 'Quantity',
19 | 'unit': 'Unit',
20 | 'remaining': 'Remaining'
21 | } %}
22 | {% set preferred_order = ['product','order_qty','unit','remaining'] %}
23 |
24 |
25 | {# First, render preferred keys that exist #}
26 | {% for k in preferred_order if k in result %}
27 |
28 | {{ label_map.get(k, k|replace('_',' ')|title) }}
29 | {{ result[k] }}
30 |
31 | {% endfor %}
32 |
33 | {# Then, render any extra keys not in preferred_order #}
34 | {% for k, v in result.items() if k not in preferred_order %}
35 |
36 | {{ label_map.get(k, k|replace('_',' ')|title) }}
37 | {{ v }}
38 |
39 | {% endfor %}
40 |
41 |
42 |
43 |
Back
44 | {% endif %}
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/static/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Segoe UI', Tahoma, sans-serif;
3 | background: linear-gradient(135deg, #1e3c72, #2a5298);
4 | color: #fff;
5 | margin: 0;
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | height: 100vh;
10 | }
11 |
12 | .container {
13 | background: rgba(255, 255, 255, 0.1);
14 | padding: 2rem;
15 | border-radius: 15px;
16 | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
17 | width: 350px;
18 | text-align: center;
19 | }
20 |
21 | h1 {
22 | margin-bottom: 1.5rem;
23 | font-size: 1.8rem;
24 | }
25 |
26 | select, input[type="number"], input[type="submit"] {
27 | width: 100%;
28 | box-sizing: border-box;
29 | padding: 0.8rem;
30 | margin: 0.5rem 0;
31 | border: none;
32 | border-radius: 8px;
33 | font-size: 1rem;
34 | }
35 |
36 | select, input[type="number"] {
37 | background: rgba(255, 255, 255, 0.2);
38 | color: #fff;
39 | }
40 |
41 | select option {
42 | color: #000;
43 | background-color: #fff;
44 | }
45 |
46 | input[type="number"]::placeholder {
47 | color: rgba(255, 255, 255, 0.7);
48 | }
49 |
50 | input[type="submit"] {
51 | background: #00c6ff;
52 | color: #000;
53 | font-weight: bold;
54 | cursor: pointer;
55 | transition: background 0.3s;
56 | }
57 |
58 | input[type="submit"]:hover {
59 | background: #0072ff;
60 | color: #fff;
61 | }
62 |
63 | /* Table styling */
64 | table {
65 | width: 100%;
66 | border-collapse: collapse;
67 | margin-top: 1rem;
68 | }
69 |
70 | th, td {
71 | padding: 0.6rem;
72 | border-bottom: 1px solid rgba(255, 255, 255, 0.3);
73 | text-align: left;
74 | }
75 |
76 | th {
77 | background-color: rgba(255, 255, 255, 0.15);
78 | font-weight: bold;
79 | }
80 |
81 | /* Back button styling */
82 | .back-button {
83 | display: inline-block;
84 | padding: 0.6rem 1.2rem;
85 | background: #00c6ff;
86 | color: #000;
87 | border-radius: 8px;
88 | text-decoration: none;
89 | font-weight: bold;
90 | transition: background 0.3s;
91 | }
92 |
93 | .back-button:hover {
94 | background: #0072ff;
95 | color: #fff;
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple_API_Example
2 | A minimal example demonstrating how to build a simple API featuring a **server** and **client** using **FastAPI**, in the context of delivering goods from New Jersey to New York.
3 |
4 | ## Video Tutorial 🎥
5 |
6 |
7 | ## Overview 📚
8 |
9 | This repository contains a small FastAPI-based server that simulates a simple delivery endpoint—sending goods from New Jersey to New York - and a client script to interact with it.
10 |
11 | It is intended as a hands-on demonstration of building and consuming RESTful APIs using FastAPI.
12 |
13 | ## Installation and Set Up ⚙️
14 |
15 | 1. Clone this repository on your local system, and navigate to project folder:
16 | ```
17 | git clone https://github.com/MariyaSha/Simple_API_Example.git
18 | cd Simple_API_Example
19 | ```
20 |
21 | 2. Create a virtual environment and install dependencies (conda + WSL example):
22 | ```
23 | conda create -n api_env python=3.12
24 | conda activate api_env
25 | pip install -r requirements.txt
26 | ```
27 |
28 | 3. In one instance of WSL terminal, run the server with:
29 | ```
30 | uvicorn nj_server:app --reload
31 | ```
32 |
33 | 4. Your server will now be available on port 8000 in your browser.
34 | To interact with your API's backend Navigate to:
35 | ```
36 | localhost:8000/docs
37 | ```
38 |
39 |
40 |
41 | Or navigate directly to an endpoint like:
42 | ```
43 | http://127.0.0.1:8000/warehouse/tomatoes?order_qty=30
44 | ```
45 |
46 |
47 |
48 | 5. While the server is running, you can use my custom client to interact with the server more conveniently.
49 | Open an additional instance of WSL (open another WSL terminal next to the existing one), activate environment, navigate to project folder, and run the client with:
50 | ```
51 | conda activate api_env
52 | cd Simple_API_Example
53 | uvicorn ny_client:app --reload --port 8001
54 | ```
55 |
56 | 6. Your client will now be available on port 8001 in your browser.
57 | To interact with your client and server, navigate to:
58 | ```
59 | localhost:8001
60 | ```
61 |
62 |
63 |
64 |
65 | 7. Once you understand how the simple implemintation works, I highly recomend to check out `nj_advanced_server.py` to see a more professional approach (using pydantic).
66 |
67 | ## Dependencies 💻
68 | - **FastAPI** — for building the API.
69 | - **Uvicorn** — ASGI server to run the app.
70 | - HTTP client libraries used in `ny_client.py`:
71 | - requests
72 | - jinja2
73 | - python-multipart
74 | - pydantic (automatically installed with fastapi)
75 |
76 | ## License 📜
77 |
78 | This project is licensed under [MIT License](https://github.com/MariyaSha/Simple_API_Example/blob/main/LICENSE.txt). Feel free to use it for experimentation or learning.
79 |
80 | ## Contact ☎️
81 |
82 | Created by **Mariya Sha** as part of a "Create Basic API" tutorial.
83 |
84 | Follow my tutorials and projects:
85 |
86 | ⭐ [YouTube – Python Simplified](https://www.youtube.com/PythonSimplified)
87 |
88 | ⭐ [LinkedIn - Mariya Sha](https://www.linkedin.com/in/mariyasha888)
89 |
90 | ⭐ [X - Mariya Sha](https://x.com/MariyaSha888)
91 |
92 |
--------------------------------------------------------------------------------