New to APIs? This guide explains core concepts in clear language, then walks you through building a small FastAPI service with essential security and testing tips. When you’re ready for advanced patterns, read the companion: Designing Secure and Scalable APIs — A Comprehensive Guide.
What Is an API?#
An API is a contract for software to talk to software. It defines how to request data or perform actions using a consistent format over HTTP.
REST vs GraphQL vs RPC (High Level)#
- REST: resources (like
users,orders) accessed via URLs and HTTP methods. - GraphQL: clients ask for exactly the fields they need in a single endpoint.
- RPC/gRPC: function-style calls for service-to-service, very fast.
For beginners, start with REST.
HTTP Essentials in 2 Minutes#
- Methods:
GET(read),POST(create),PATCH(update),DELETE(remove). - URLs:
https://api.example.com/v1/items/123. - Headers: metadata like
Authorization,Content-Type. - Status codes: 2xx success, 4xx client errors, 5xx server errors.
- JSON: common data format:
{ "name": "Notebook", "price": 9.99 }.
A Minimal FastAPI Service#
from typing import Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="API 101", version="1.0.0")
class ItemIn(BaseModel):
name: str = Field(min_length=1, max_length=100)
price: float = Field(ge=0)
description: Optional[str] = Field(default=None, max_length=280)
class ItemOut(BaseModel):
id: str
name: str
price: float
DB: dict[str, ItemOut] = {}
@app.get("/v1/health")
async def health() -> dict:
return {"status": "ok"}
@app.post("/v1/items", response_model=ItemOut, status_code=201)
async def create_item(item: ItemIn) -> ItemOut:
new_id = f"it_{len(DB)+1}"
output = ItemOut(id=new_id, name=item.name, price=item.price)
DB[new_id] = output
return output
@app.get("/v1/items/{item_id}", response_model=ItemOut)
async def get_item(item_id: str) -> ItemOut:
if item_id not in DB:
raise HTTPException(status_code=404, detail="item_not_found")
return DB[item_id]Request/Response Flow#
sequenceDiagram
autonumber
participant U as Client
participant A as FastAPI
participant D as In-memory DB
U->>A: POST /v1/items { name, price }
A->>A: Validate JSON (Pydantic)
A->>D: Save item
D-->>A: OK
A-->>U: 201 Created + Item JSONInput Validation Basics#
- Validate types and ranges (e.g., price >= 0).
- Keep strings bounded (e.g., name <= 100 chars).
- Return clear errors with
400/422for invalid requests.
Errors You’ll See Early#
400/422when the body is malformed or fields fail validation.404when an ID doesn’t exist.405when the method is wrong (e.g.,PUTon aGETendpoint).
Tiny Decision Tree#
flowchart TD
A[Request] --> B{Valid input?}
B -- No --> E[400/422]
B -- Yes --> C{Resource exists?}
C -- No --> F[404]
C -- Yes --> D[2xx]Auth 101: API Keys vs Bearer Tokens#
- API Key: static secret string in header
X-API-Key. Simple, rotate often. - Bearer Token (JWT): signed token with claims (who you are, scopes). More flexible.
from fastapi import Header, Depends
def require_api_key(x_api_key: str = Header(alias="X-API-Key")) -> None:
if x_api_key != "demo_secret":
raise HTTPException(status_code=401, detail="invalid_api_key")
@app.get("/v1/private", dependencies=[Depends(require_api_key)])
async def private_resource() -> dict:
return {"message": "authorized"}CORS for Browser Apps#
If your frontend runs on a different domain, enable CORS.
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type", "X-API-Key"],
)Pagination and Filtering (At a Glance)#
- Offset:
GET /v1/items?offset=0&limit=20— simple, good for small sets. - Cursor:
GET /v1/items?cursor=abc&limit=20— better for changing data.
Idempotency (Why It Matters)#
If clients retry a POST, you don’t want duplicate creations. Accept X-Idempotency-Key and reuse the original result for the same key.
Rate Limiting (Conceptual)#
Protect your API with per-user or per-IP limits. On limit exceeded, return 429 with Retry-After seconds.
Testing Your API#
curlquick checks, Postman/Insomnia for collections.- Read logs and status codes; verify headers and JSON.
Handy curl Examples#
curl -i https://api.example.com/v1/health
curl -i -X POST https://api.example.com/v1/items \
-H 'Content-Type: application/json' \
-d '{"name":"Pen","price":1.25}'
curl -i https://api.example.com/v1/items/it_1Beginner Hardening Checklist#
- Validate all inputs with Pydantic.
- Set
Content-Type: application/jsonin responses. - Turn on CORS only for known origins.
- Use API keys or bearer tokens; never accept secrets in query strings.
- Add basic rate limiting and idempotency for writes.
- Log a
request_idand include it in responses.
Glossary#
- JWT: signed token proving identity and permissions.
- ETag: a version tag for caching and concurrency.
- CORS: browser rule for cross-origin requests.
- 2xx/4xx/5xx: success/client/server status code families.
What’s Next#
Ready to go deeper? See the advanced guide for versioning, ETags, RBAC, rate limiting, webhooks security, observability, and more: Designing Secure and Scalable APIs — A Comprehensive Guide.