Skip to main content
  1. Blogs/

Beginners Guide to Building and Securing APIs

·771 words·4 mins·
Subhajit Bhar
Author
Subhajit Bhar
I build production-grade document extraction pipelines for businesses that process invoices, lab reports, contracts, and other document types at scale.
Table of Contents

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.

TL;DR

  • An API is a contract for software-to-software communication over HTTP.
  • FastAPI is the fastest way to build a Python REST API — validation, docs, and async built in.
  • Always validate input with Pydantic models; never trust raw request data.
  • Add authentication (API keys or JWT) and rate limiting before any public exposure.
  • For production patterns, read the companion: Designing Secure and Scalable APIs.

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 JSON

Input Validation Basics
#

  • Validate types and ranges (e.g., price >= 0).
  • Keep strings bounded (e.g., name <= 100 chars).
  • Return clear errors with 400/422 for invalid requests.

Errors You’ll See Early
#

  • 400/422 when the body is malformed or fields fail validation.
  • 404 when an ID doesn’t exist.
  • 405 when the method is wrong (e.g., PUT on a GET endpoint).

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
#

  • curl quick 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_1

Beginner Hardening Checklist
#

  • Validate all inputs with Pydantic.
  • Set Content-Type: application/json in 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_id and 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.

Related

Designing Secure and Scalable APIs — A Comprehensive Guide

·1487 words·7 mins
APIs are the connective tissue of modern products. This guide distills proven practices for API design, security, observability, and reliability—covering the most frequent questions and edge cases teams face in production. Examples use FastAPI and Pydantic v2, but the principles generalize to any stack.

Git & GitHub: The Definitive Version Control Guide

·1559 words·8 mins
Version control is the foundation of reliable software delivery. This guide teaches Git from first principles, then layers in practical GitHub workflows used by high-performing teams. You’ll learn the mental models, the everyday commands, and the advanced tools to collaborate confidently without fear of breaking anything.

Git 101 – Commands and Workflows Cheat Sheet

·448 words·3 mins
A quick, task-oriented Git reference. Pair this with the in-depth guide for concepts and best practices. TL;DR Working tree → git add → Staging area → git commit → Local repo → git push → Remote. Most common daily commands: status, add, commit, push, pull, checkout, merge. Undo staged changes: git restore --staged <file>. Undo last commit (keep changes): git reset HEAD~1. For concepts and workflows, read the full Git guide. Minimal Mental Model # graph LR WD[Working Dir] -- add --> ST[Staging] ST -- commit --> REPO[Local Repo] REPO -- push --> ORI[Origin] ORI -- fetch/pull --> REPO Setup # git --version git config --global user.name "Your Name" git config --global user.email "you@example.com" git config --global init.defaultBranch main Create or Clone # git init git clone <url> Status and Diffs # git status git diff # unstaged git diff --staged # staged vs HEAD Stage and Commit # git add <path> git add -p # interactive hunks git commit -m "feat: message" git commit --amend # edit last commit Branching # git branch git switch -c feature/x git switch main git branch -d feature/x Sync with Remote # git remote -v git fetch git pull # merge git pull --rebase # rebase git push -u origin my-branch Merge vs Rebase # git switch my-branch && git merge main git switch my-branch && git rebase main Resolve Conflicts # git status # edit files, remove markers git add <file> git commit # after merge git rebase --continue # during rebase Stash Work # git stash push -m "wip" git stash list git stash pop Undo Safely # git restore --staged <file> # unstage git restore <file> # discard local edits git revert <sha> # new commit to undo git reset --soft HEAD~1 # keep changes, drop last commit git reflog # find lost commits Tags and Releases # git tag -a v1.0.0 -m "msg" git push --tags Ignore and Clean # echo "node_modules/" >> .gitignore git clean -fdx # dangerous: removes untracked files Authentication (Quick) # # HTTPS + PAT git clone https://github.com/owner/repo.git # SSH ssh-keygen -t ed25519 -C "you@example.com" ssh-add ~/.ssh/id_ed25519 git clone git@github.com:owner/repo.git Conventional Commits (Optional) # feat(auth): add oauth login fix(api): handle null pointer in user service chore(ci): update node to 20 Common One-Liners # # See last commit summary git log -1 --stat # Interactive rebase last 5 commits git rebase -i HEAD~5 # Squash branch onto main git switch my-branch && git rebase -i main Quick PR Flow (GitHub) # git switch -c feat/x # edit, add, commit git push -u origin feat/x # open PR on GitHub See also: the full guide “The Definitive Guide to Version Control with Git and GitHub”.