API Academy
🌐 English
  • 🌐 English
  • 🌐 繁體中文
HomePetstore APIExplore more APIs
HomePetstore APIExplore more APIs
🌐 English
  • 🌐 English
  • 🌐 繁體中文
🌐 English
  • 🌐 English
  • 🌐 繁體中文
  1. Developing APIs
  • Introduction
  • Table of Contents
  • API Academy
    • Get Started
      • What is an API?
      • How Does an API Work?
      • How to Call an API?
      • How to Read an API Documentation?
      • Chapter Summary
      • Get realtime weather
    • API Fundamentals
      • API Funtamentals: Overview
      • Method & Path
      • Parameters
      • Request Body
      • Responses
      • API Specification & OAS
      • Chapter Summary
    • Working with APIs
      • Working with APIs: Overview
      • Making Requests from Spec
      • Environments and Variables
      • Chaining Multiple Endpoints
      • Handling Authentication
      • Handling API Signatures
      • Introduction to Scripts
      • Chapter Summary
    • Mocking APIs
      • Mocking APIs: Overview
      • Smart Mock
      • Mock Expectations
      • Cloud Mock
      • Mock Scripts
      • Chapter Summary
    • Designing APIs
      • Designing APIs: Overview
      • Introduction to API Design
      • Creating Your First API Project
      • Analyzing Requirements and Planning Your API
      • Designing Data Models
      • Designing Endpoints
      • Using Components and Reusability
      • Setting Up Authentication
      • API Design Guidelines
      • Chapter Summary
    • Developing APIs
      • Developing APIs: Overview
      • Setup: Install Your AI Coding Assistant
      • Quick Start: From Spec to Running API in 30 Minutes
      • Understanding the Generated Code
      • Testing Your API with Apidog
      • Deployment: Put Your API Online
      • Chapter Summary
    • Testing APIs
      • Testing APIs: Overview
      • Getting Started: Your First Test Scenario
      • Integration Testing and Data Passing
      • Dynamic Values
      • Assertions and Validations
      • Flow Control: If, For, ForEach
      • Data-Driven Testing
      • Performance Testing
      • Test Reports and Analysis
      • CI/CD Integration
      • Scheduled Tasks and Automation
      • Advanced Testing Strategies
      • Chapter Summary
    • API Documentations
      • API Documentations: Overview
      • Publishing Your First API Doc
      • Customizing Documentation Appearance
      • Interactive Features for Consumers
      • Advanced Publishing Settings
      • Managing API Versions
      • Chapter Summary
    • Advanced API Technologies
      • API Technologies: Overview
      • GraphQL
      • gRPC
      • WebSocket
      • Socket.IO
      • Server-Sent Events (SSE)
      • SOAP
      • Chapter Summary
    • API Lifecycle
      • API Lifecycle: Overview
      • Stages of the API Lifecycle
      • API Governance
      • API Security Best Practices
      • Monitoring and Analytics
      • API Versioning Strategies
      • The Future of APIs
      • Chapter Summary
    • API Security
      • API Security: Overview
      • API Security Fundamentals
      • Authentication vs Authorization
      • Understanding OAuth 2.0 and OpenID Connect
      • JSON Web Tokens (JWT)
      • OWASP API Security Top 10
      • Encryption and HTTPS
      • Chapter Summary
    • API Tools
      • API Tools: Overview
      • The Evolution of API Tools
      • API Clients
      • Command Line Tools (cURL, HTTPie)
      • API Design and Documentation Tools
      • API Mocking Tools
      • API Testing Tools
      • All-in-One API Platforms
      • Chapter Summary
    • API Gateway
      • API Gateway: Overview
      • What is an API Gateway?
      • Key Features of API Gateways
      • API Gateway vs Load Balancer vs Service Mesh
      • Popular API Gateway Solutions
      • The BFF (Backend for Frontend) Pattern
      • Chapter Summary
  • Modern Pet Store
    • Pet
      • Get Pet
      • Update Pet
      • Delete Pet
      • Create Pet
      • List Pets
      • Upload Pet Image
    • User
      • Update User
      • Get User
      • Delete User
      • Login
      • Logout
      • Create User
    • Store
      • List Inventory
      • Create Order
      • Get Order
      • Delete Order
      • Callback Example
      • Pay for an Order
    • Payments
      • Pay Order
    • Chat
      • Create Chat Completion
    • Webhooks
      • Pet Adopted Event
      • New Pet Available Event
  • Schemas
    • Pet
    • Category
    • User
    • ApiResponse
    • OrderPayment
    • Tag
    • Order
    • Links-Order
    • PetCollection
    • Bank Card
    • Bank Account
    • Links
    • Error
HomePetstore APIExplore more APIs
HomePetstore APIExplore more APIs
🌐 English
  • 🌐 English
  • 🌐 繁體中文
🌐 English
  • 🌐 English
  • 🌐 繁體中文
  1. Developing APIs

Understanding the Generated Code

Your API is running. You can see it working at http://localhost:8000/docs. But what's actually happening in all those files Cursor generated?
This chapter walks through the code so you understand what you're working with. We'll explore each file, explain the key concepts, and show you how to use Cursor itself as a learning tool. By the end, you'll know how your API actually works.

Using Cursor to Learn Code#

Before we dive in, here's a technique that makes understanding code much easier: ask Cursor to explain it.
Seriously. The AI that wrote the code can explain the code. Here's how:
1.
Select any piece of code you don't understand
2.
Press Cmd+K (Mac) or Ctrl+K (Windows/Linux)
3.
Ask: "Explain what this code does" or "Why do we need this?"
4.
Cursor will explain it in plain language
You can also:
Ask "What does this function do?"
Ask "How does this work with the database?"
Ask "Why is this written this way?"
Use this liberally as we go through the files. I'll explain the big picture, but if you want more detail on any specific part, just ask Cursor.

Project Structure Overview#

Open your project in Cursor. You should see something like this:
user-api/
β”œβ”€β”€ main.py              # Entry point - starts the API
β”œβ”€β”€ database.py          # Database setup and connection
β”œβ”€β”€ auth.py              # JWT token functions
β”œβ”€β”€ dependencies.py      # Reusable components
β”œβ”€β”€ models/
β”‚   └── user.py         # Database table definition
β”œβ”€β”€ schemas/
β”‚   └── user.py         # API request/response formats
β”œβ”€β”€ routers/
β”‚   └── users.py        # All your endpoints
β”œβ”€β”€ requirements.txt     # Dependencies
β”œβ”€β”€ .env.example        # Environment variables template
β”œβ”€β”€ README.md           # Setup instructions
└── users.db            # SQLite database (appears after first run)
Think of this structure in layers:
Entry point (main.py) creates the API and connects everything.
Routers (routers/users.py) handle HTTP requests for each endpoint.
Schemas (schemas/user.py) define what data looks like in requests and responses.
Models (models/user.py) define what data looks like in the database.
Database (database.py) manages the connection to SQLite.
Auth (auth.py) handles JWT tokens for authentication.
Dependencies (dependencies.py) provides reusable functions like "get current user".
Let's walk through each file.

main.py - The Entry Point#

Open main.py. This is where everything starts.
What you'll see:
What's happening:
The file creates your FastAPI application. That's the app = FastAPI() line. Everything else is configuration.
Base.metadata.create_all() creates your database tables automatically. The first time you run the API, it sees you don't have a users table and creates it. This is why you don't need to run separate database setup commands.
The CORS middleware allows your API to receive requests from web browsers. In development, it's wide open (allow_origins=["*"]). In production, you'd restrict this to specific domains.
app.include_router(users.router) connects all your user endpoints. Without this line, the endpoints wouldn't be accessible.
That's it. The entry point is surprisingly simple because all the real work happens in other files.

database.py - Database Connection#

This file sets up the connection to your SQLite database.
What you'll see:
Understanding the pieces:
engine is your database connection. It knows how to talk to SQLite.
SessionLocal creates database sessions. Each time you want to interact with the database, you create a session, do your work, and close it.
Base is what your models inherit from. Remember models/user.py? It uses this Base to become a database table.
get_db() is a function that gives you a database session and automatically closes it when you're done. This is used throughout your API via FastAPI's dependency injection.
Why SQLite?
For development, SQLite is perfect. It's just a file (users.db) with no separate server to manage. When you deploy to production, you'll change one line to use PostgreSQL instead. The rest of the code stays the same because SQLAlchemy (the ORM) abstracts away the database differences.

models/user.py - Database Table#

This defines what your users table looks like in the database.
What you'll see:
This is your database schema. Each Column becomes a column in the users table.
Some interesting details:
id uses UUID (Universally Unique Identifier) which looks like "a8f3c2e1-4b7d-4e9a-b3c1-8f2d3e4a5b6c". The default=lambda: str(uuid.uuid4()) generates a new random ID automatically when you create a user.
email has unique=True which means the database will reject duplicate emails. It also has index=True for faster lookups.
nullable=False means the field is required. nullable=True means it's optional.
Notice there's a password column. This stores the hashed password (not the plain text). We'll see how hashing works when we look at the routers.
Models vs Schemas:
Don't confuse models/user.py with schemas/user.py:
Models define database tables (what's stored)
Schemas define API data formats (what's sent/received)
They're similar but serve different purposes. You'll see why we need both in a moment.

schemas/user.py - API Data Formats#

This defines what data looks like in your API requests and responses.
What you'll see:
Why three different schemas?
Each one serves a specific purpose:
UserCreate is what clients send when creating a user. It includes the password because they need to set it. It validates that email is actually an email format (EmailStr).
UserResponse is what the API returns. Notice it does not include password. This is a security feature. The password field is write-only - you can send it in, but you never get it back.
UserUpdate is for updating users. All fields are Optional because you might only want to update firstName without changing anything else.
Pydantic's role:
These schemas use Pydantic, which automatically validates data. If someone sends "email": "not-an-email", Pydantic rejects it before your code even runs. If they send "age": 25 but age isn't in your schema, Pydantic ignores it.
This is why FastAPI is so powerful - you get automatic validation and automatic documentation just by defining these schemas.

routers/users.py - Your Endpoints#

This is where the real work happens. Open routers/users.py and you'll see all six endpoints.
Let's walk through a few key ones:

POST /users - Create User#

Breaking it down:
@router.post("/users") says "this function handles POST requests to /users"
response_model=UserResponse tells FastAPI to format the response using UserResponse schema (which excludes password)
status_code=201 sets the HTTP status code for successful creation
user: UserCreate means FastAPI will automatically parse the request body as JSON and validate it against the UserCreate schema
db: Session = Depends(get_db) is FastAPI's dependency injection. It calls get_db() from database.py, which gives you a database session. When the function ends, the session automatically closes.
The logic flow:
1.
Check if email already exists (to prevent duplicates)
2.
Hash the password using bcrypt (never store plain text)
3.
Create a User model instance with the data
4.
Add it to the database session
5.
Commit the transaction (actually saves to database)
6.
Refresh to get the generated fields (like id, createdAt)
7.
Return the user (FastAPI automatically converts it to UserResponse format)

POST /user/login - Login#

What's happening:
Find the user by email. If not found, return 401 (don't tell the client whether the email exists or password is wrong - that's a security practice).
Verify the password using bcrypt. It hashes the submitted password and compares it to the stored hash.
If valid, create a JWT token containing the user's ID (sub is the standard JWT field for "subject").
Return the token. The client will include this in the Authorization header for protected endpoints.

PUT /users/{id} - Update User (Protected)#

Notice the difference:
current_user: User = Depends(get_current_user) - This endpoint requires authentication. The get_current_user dependency extracts the JWT token from the Authorization header, validates it, and loads the user. If the token is invalid or missing, the request fails before this function even runs.
if current_user.id != id: - Users can only update their own account. Even with a valid token, you can't modify someone else's data.
user_update.dict(exclude_unset=True) - Only update fields that were actually provided. If the client sends {"firstName": "NewName"}, only firstName changes. The exclude_unset=True ignores fields that weren't in the request.

auth.py - JWT Token Handling#

This file has the authentication logic.
What you'll see:
Understanding JWT:
When a user logs in, create_access_token() creates a JWT token. This token is a cryptographically signed string that contains the user ID and expiration time. The signature uses SECRET_KEY so tokens can't be forged.
The token expires after 24 hours (ACCESS_TOKEN_EXPIRE_HOURS). After that, the user needs to log in again.
Understanding password hashing:
hash_password() takes a plain password like "SecurePass123" and turns it into something like "$2b$12$KIXxE.j3vFg9QN8hA6Fv0e...". This is a one-way transformation - you can't reverse it to get the original password.
verify_password() takes a plain password and a hash, hashes the plain password, and compares the hashes. If they match, the password is correct.
This means even if your database is compromised, attackers don't get actual passwords.

dependencies.py - Reusable Components#

What this does:
HTTPBearer() extracts the Authorization: Bearer <token> header from requests.
get_current_user() is a dependency that protected endpoints use. It:
1.
Gets the token from the Authorization header
2.
Decodes and validates the JWT
3.
Extracts the user ID from the token
4.
Loads the user from the database
5.
Returns the user object
If any step fails (missing token, invalid signature, expired token, user deleted), it raises a 401 error.
This is why protected endpoints just use Depends(get_current_user) and automatically have access to the authenticated user. The dependency system is incredibly elegant.

How It All Fits Together#

Let's trace a complete request through the system. Say a client wants to update their name:
1. Request arrives:
PUT /users/abc123
Authorization: Bearer eyJhbGc...
{"firstName": "NewName"}
2. FastAPI routing (in main.py) sees this matches the PUT /users/{id} endpoint in routers/users.py
3. Dependency injection runs:
get_db() creates a database session
get_current_user() validates the JWT token and loads the user
4. Pydantic validation parses the request body as UserUpdate schema and validates it
5. The endpoint function runs:
Checks if current user matches the ID
Updates the user's firstName
Commits to database
6. Response formatting:
FastAPI converts the User model to UserResponse schema
Excludes the password field
Returns JSON
7. Client receives:
{
  "id": "abc123",
  "email": "user@example.com",
  "firstName": "NewName",
  ...
}
All of this happens automatically. You just write the business logic in the endpoint function.

Key Concepts Summary#

Now that you've seen the code, here are the important patterns:
Three-layer architecture:
Routers handle HTTP (requests and responses)
Schemas define API data formats (validation)
Models define database structure (persistence)
Dependency injection:
FastAPI's Depends() automatically provides database sessions, current user, etc. This keeps code clean and handles setup/cleanup.
Automatic validation:
Pydantic schemas validate all input automatically. Bad data never reaches your code.
Security by design:
Passwords are hashed (bcrypt)
Passwords never appear in responses (schema exclusion)
JWT tokens expire (time-limited access)
Users can only modify their own data (authorization checks)
Database abstraction:
SQLAlchemy ORM means you write Python, not SQL. Switching from SQLite to PostgreSQL is just changing the connection string.

What You've Learned#

You now understand:
How the project is structured and why
What each file does and how they work together
How requests flow through the API
How authentication works (JWT)
How security is built in (hashing, validation, authorization)
How the database integration works
More importantly, you know how to use Cursor to explore code. Select anything you don't understand, press Cmd+K, and ask. The AI that generated this code is your learning companion.

What's Next#

Understanding the code is one thing. Making it yours is another.
In the next chapter, you'll learn how to customize and extend this API. Want to add new fields? Change how something works? Add entirely new features? We'll show you how to use Cursor to make those changes quickly and correctly.
Continue with β†’ Testing Your API in Apidog
First, let's make sure everything actually works by testing it properly in Apidog.
Modified atΒ 2025-12-29 10:42:25
Previous
Quick Start: From Spec to Running API in 30 Minutes
Next
Testing Your API with Apidog
Built with