├── 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 |
11 | 16 | 17 | 18 |
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 | 29 | 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 | 37 | 38 | 39 | {% endfor %} 40 |
{{ label_map.get(k, k|replace('_',' ')|title) }}{{ result[k] }}
{{ label_map.get(k, k|replace('_',' ')|title) }}{{ v }}
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 | API with FastAPI thumbnail 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 | FastAPI Swagger UI Backend Screenshot 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 | FastAPI Raw Endpoint Screenshot 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 | Custom Client Screenshot - Order Products 62 |
63 | Custom Client Screenshot - Shipping Confirmation 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 | --------------------------------------------------------------------------------