| PolarSPARC |
Quick Primer on FastAPI
| Bhaskar S | 10/05/2025 |
Overview
FastAPI is a high performance Python web framework for building REST based API services.
In this primer, we will demonstrate how one can setup and use the FastAPI web framework.
Installation and Setup
The installation and setup will be on a Ubuntu 24.04 LTS based Linux desktop. Ensure that Python 3.x programming language is installed and setup on the desktop.
To install the necessary Python packages, execute the following command in a terminal window:
$ pip install fastapi httpie python-jose uvicorn
For our demonstration, we will create a directory called MyFastAPI under the users home directory by executing the following command in a terminal window:
$ mkdir -p $HOME/MyFastAPI
This completes all the necessary installation and setup for the FastAPI hands-on demonstration.
Hands-on with FastAPI
In the following sections, we will get our hands dirty with the FastAPI framework. So, without further ado, let us get started !!!
The following is our first FastAPI Python code:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 28 Sep 2025
#
import logging
import uvicorn
from fastapi import FastAPI
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-01')
app = FastAPI()
@app.get('/')
async def index():
logger.info('Calling index ...')
return {'message': 'Welcome to MyFastAPI!'}
def main():
logger.info('Starting web server on port 8000')
uvicorn.run(app, host='192.168.1.25', port=8000)
if __name__ == '__main__':
main()
To execute the above Python code, execute the following command in a terminal window:
$ python sample_1.py
The following would be the typical output:
INFO 2025-10-04 21:28:42,447 - Starting web server on port 8000 INFO: Started server process [61685] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://192.168.1.25:8000 (Press CTRL+C to quit)
Open a terminal window and execute the following command:
$ http GET http://192.168.1.25:8000/
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 35
content-type: application/json
date: Sun, 05 Oct 2025 01:29:47 GMT
server: uvicorn
{
"message": "Welcome to MyFastAPI!"
}
The code from sample_1.py above needs some explanation:
An instance of the class FastAPI represents a web application and is captured by the Python variable app in the code.
The app variable can be used as a Python URI path decorator by invoking the desired HTTP verb (get, post, put, delet, etc) to indicate to FastAPI that the HTTP request for that specific URI be routed to that specific Python handler function. In our example, the HTTP GET request on the URI "/" will be routed to the Python function index().
The function ubvicorn.run() takes as input an instance of FastAPI and launches a web server on the specifc host (192.168.1.25) on the port 8000 .
Next, the following is our second FastAPI Python code that demonstrates how one can organize and structure a group of related API endpoints into separate Python module(s). with different HTTP response types:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 28 Sep 2025
#
import logging
from fastapi import APIRouter
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-02')
router = APIRouter()
@router.get('/greet/{name}')
async def greet(name: str):
logger.info('Calling greet with name ...')
return {'message': 'Hello ' + name +' !'}
@router.get('/greet/spanish/{name}')
async def greet(name: str):
logger.info('Calling greet in Spanish with name ...')
return {'message': 'Hola ' + name +' !'}
An instance of the class APIRouter represents a grouping of related API endpoint(s), which can be defined in separate Python module file(s) to manage complexity.
The following is our third FastAPI Python code that demonstrates how one can include related API endpoints that are defined in separate Python module file(s):
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 28 Sep 2025
#
import logging
import uvicorn
from fastapi import FastAPI
from MyFastAPI import sample_2
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-03')
app = FastAPI()
app.include_router(sample_2.router)
@app.get('/')
async def index():
logger.info('Calling index ...')
return {'message': 'Welcome to MyFastAPI!'}
def main():
logger.info('Starting web server on port 8000')
uvicorn.run(app, host='192.168.1.25', port=8000)
if __name__ == '__main__':
main()
Notice the use of the call app.include_router(), which includes router endpoint(s) defined in other Python module(s), such as the module sample_2 in our example.
To execute the above Python code, execute the following command in a terminal window:
$ python sample_3.py
The output would be similar to that of Output.1 from above.
Open a terminal window and execute the following command:
$ http GET http://192.168.1.25:8000/greet/ada
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 25
content-type: application/json
date: Sun, 05 Oct 2025 19:35:05 GMT
server: uvicorn
{
"message": "Hello ada !"
}
In the same terminal window, now execute the following command:
$ http GET http://192.168.1.25:8000/greet/spanish/james
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 26
content-type: application/json
date: Sun, 05 Oct 2025 19:35:44 GMT
server: uvicorn
{
"message": "Hola james !"
}
Moving along, the following is our fourth FastAPI Python code that demonstrates how one can raise HTTP exception(s):
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 28 Sep 2025
#
import logging
from fastapi import APIRouter, HTTPException, status
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-04')
router = APIRouter()
@router.get('/greet')
async def greet_1(name: str):
logger.info('Calling greet ...')
if name is None:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail='Name cannot be None')
return {'message': 'Hello ' + name +' !'}
@router.get('/greet/{name}')
async def greet_2(name: str):
logger.info('Calling greet with name ...')
return {'message': 'Hello ' + name +' !'}
Notice the use of the class HTTPException to raise an exception with a specific HTTP status code and reason.
The following is our fifth FastAPI Python code which demonstrates how one can handle the HTTP exception(s) thrown from a handler function and send a JSON response back to the client:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 28 Sep 2025
#
import logging
import uvicorn
from fastapi import FastAPI, HTTPException
from starlette.responses import JSONResponse
from MyFastAPI import sample_4
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-05')
app = FastAPI()
app.include_router(sample_4.router)
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
return JSONResponse(
status_code=exc.status_code,
content={
'message': exc.detail,
})
@app.get('/')
async def index():
logger.info('Calling index ...')
return {'message': 'Welcome to MyFastAPI!'}
def main():
logger.info('Starting web server on port 8000')
uvicorn.run(app, host='192.168.1.25', port=8000)
if __name__ == '__main__':
main()
Notice the use of the decorator @app.exception_handler(HTTPException) on the Python function, which handles any of the HTTP exception(s) raised and returns a JSON response to the client with an appropriate HTTP status code.
To execute the above Python code, execute the following command in a terminal window:
$ python sample_5.py
The output would be similar to that of Output.1 from above.
Open a terminal window and execute the following command:
$ http GET http://192.168.1.25:8000/greet
The following would be the typical output:
HTTP/1.1 422 Unprocessable Entity
content-length: 90
content-type: application/json
date: Sun, 05 Oct 2025 21:34:51 GMT
server: uvicorn
{
"detail": [
{
"input": null,
"loc": [
"query",
"name"
],
"msg": "Field required",
"type": "missing"
}
]
}
Notice the HTTP status code of 422 along with a JSON response above !!!
In the same terminal window, now execute the following command:
$ http GET http://192.168.1.25:8000/greet?name=Dennis
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 28
content-type: application/json
date: Sun, 05 Oct 2025 21:37:23 GMT
server: uvicorn
{
"message": "Hello Dennis !"
}
Shifting gears, the following is our sixth FastAPI Python code that demonstrates how one can implement a REST based API service:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 28 Sep 2025
#
import logging
import uvicorn
from fastapi import FastAPI, HTTPException, status
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel, Field, EmailStr
from starlette.responses import JSONResponse
from typing import Optional
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-06')
class Contact(BaseModel):
firstname: str = Field(min_length=1, max_length=25)
lastname: str = Field(min_length=1, max_length=25)
email: EmailStr = Field(min_length=10, max_length=50)
mobile: Optional[str] | None = Field(max_length=10, pattern=r'^[1-9][0-9]+$', default=None)
address_book = {
'ada_l@mail.com': Contact(firstname='Ada',
lastname='Lovelace',
email='ada_l@mail.com',
mobile='1230981000'),
'dennis.ritchie@att.com': Contact(firstname='Dennis',
lastname='Ritchie',
email='dennis.ritchie@att.com'),
'james_g@sun.com': Contact(firstname='James',
lastname='Gosling',
email='james_g@sun.com'),
}
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def request_validation_error_handler(request, exc):
logger.info('Calling request_validation_error_handler ...')
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
'message': exc.errors(),
})
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
logger.info('Calling http_exception_handler ...')
return JSONResponse(
status_code=exc.status_code,
content={
'message': exc.detail,
})
@app.get('/contacts')
async def all_contacts() -> list[Contact]:
logger.info('Calling all_contacts ...')
return address_book.values()
@app.post('/contact')
async def create_contact(contact: Contact):
logger.info(f'Calling create_contact with {contact}')
if contact is not None and contact.email is not None:
address_book[contact.email] = contact
logger.info(f'Created {contact}')
return contact
@app.put('/contact')
async def update_contact(contact: Contact) -> Contact:
logger.info(f'Calling update_contact with {contact}')
if contact is not None and contact.email is not None:
if contact.email not in address_book:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail='Email not found')
address_book[contact.email] = contact
logger.info(f'Updated {contact}')
return contact
@app.delete('/contact')
async def delete_contact(email: str):
logger.info(f'Calling delete_contact with {email}')
if email is not None:
del address_book[email]
logger.info(f'Deleted {email}')
return {'email': email}
def main():
logger.info('Starting web server on port 8000')
uvicorn.run(app, host='192.168.1.25', port=8000)
if __name__ == '__main__':
main()
Notice the use of the decorator @app.exception_handler(RequestValidationError) on the Python function, which handles any of the exception(s) raised due to invalid request and returns a JSON response to the client with an appropriate HTTP status code.
To execute the above Python code, execute the following command in a terminal window:
$ python sample_6.py
The output would be similar to that of Output.1 from above.
Open a terminal window and execute the following command:
$ http GET http://192.168.1.25:8000/contacts
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 264
content-type: application/json
date: Sun, 05 Oct 2025 21:48:05 GMT
server: uvicorn
[
{
"email": "ada_l@mail.com",
"firstname": "Ada",
"lastname": "Lovelace",
"mobile": "1230981000"
},
{
"email": "dennis.ritchie@att.com",
"firstname": "Dennis",
"lastname": "Ritchie",
"mobile": null
},
{
"email": "james_g@sun.com",
"firstname": "James",
"lastname": "Gosling",
"mobile": null
}
]
In the same terminal window, now execute the following command:
$ echo '{"firstname": "Guido Van", "lastname": "Rossum", "email": "guido_van_rossum@python.org", "mobile": "2348762000"}' | http POST http://192.168.1.25:8000/contact
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 105
content-type: application/json
date: Sun, 05 Oct 2025 21:49:46 GMT
server: uvicorn
{
"email": "guido_van_rossum@python.org",
"firstname": "Guido Van",
"lastname": "Rossum",
"mobile": "2348762000"
}
Once again, in the same terminal window, now execute the following command:
$ echo '{"firstname": "James", "lastname": "Gosling", "email": "james_g.sun.com", "mobile": "3457653000"}' | http PUT http://192.168.1.25:8000/contact
The following would be the typical output:
HTTP/1.1 422 Unprocessable Entity
content-length: 224
content-type: application/json
date: Sun, 05 Oct 2025 21:55:38 GMT
server: uvicorn
{
"message": [
{
"ctx": {
"reason": "An email address must have an @-sign."
},
"input": "james_g.sun.com",
"loc": [
"body",
"email"
],
"msg": "value is not a valid email address: An email address must have an @-sign.",
"type": "value_error"
}
]
}
One more time, in the same terminal window, now execute the following command:
$ echo '{"firstname": "James", "lastname": "Gosling", "email": "james_g@sun.com", "mobile": "3457653000"}' | http PUT http://192.168.1.25:8000/contact
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 90
content-type: application/json
date: Sun, 05 Oct 2025 21:56:52 GMT
server: uvicorn
{
"email": "james_g@sun.com",
"firstname": "James",
"lastname": "Gosling",
"mobile": "3457653000"
}
One more time, in the same terminal window, now execute the following command:
$ http GET http://192.168.1.25:8000/contacts
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 378
content-type: application/json
date: Sun, 05 Oct 2025 21:57:59 GMT
server: uvicorn
[
{
"email": "ada_l@mail.com",
"firstname": "Ada",
"lastname": "Lovelace",
"mobile": "1230981000"
},
{
"email": "dennis.ritchie@att.com",
"firstname": "Dennis",
"lastname": "Ritchie",
"mobile": null
},
{
"email": "james_g@sun.com",
"firstname": "James",
"lastname": "Gosling",
"mobile": "3457653000"
},
{
"email": "guido_van_rossum@python.org",
"firstname": "Guido Van",
"lastname": "Rossum",
"mobile": "2348762000"
}
]
Again, in the same terminal window, now execute the following command:
$ http DELETE http://192.168.1.25:8000/contact?email='guido_van_rossum@python.org'
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 39
content-type: application/json
date: Mon, 06 Oct 2025 00:21:10 GMT
server: uvicorn
{
"email": "guido_van_rossum@python.org"
}
One last time, in the same terminal window, now execute the following command:
$ http GET http://192.168.1.25:8000/contacts
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 272
content-type: application/json
date: Mon, 06 Oct 2025 00:21:55 GMT
server: uvicorn
[
{
"email": "ada_l@mail.com",
"firstname": "Ada",
"lastname": "Lovelace",
"mobile": "1230981000"
},
{
"email": "dennis.ritchie@att.com",
"firstname": "Dennis",
"lastname": "Ritchie",
"mobile": null
},
{
"email": "james_g@sun.com",
"firstname": "James",
"lastname": "Gosling",
"mobile": "3457653000"
}
]
Next on, the following is our seventh FastAPI Python code that demonstrates how one can protect a REST based API service endpoint using a JWT token:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 04 Oct 2025
#
import logging
import os
import uvicorn
from datetime import datetime, timezone, timedelta
from dotenv import load_dotenv, find_dotenv
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from pydantic import BaseModel
logging.basicConfig(format='%(levelname)s %(asctime)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger('sample-07')
class Credentials(BaseModel):
username: str
password: str
class JoseJwtManager(object):
def __init__(self):
load_dotenv(find_dotenv())
self.jwt_secret_key = os.getenv('JWT_SECRET_KEY')
self.jwt_algorithm = os.getenv('JWT_ALGORITHM')
self.jwt_expiration_secs = int(os.getenv('JWT_EXPIRATION_SECS'))
def expiry_time_secs(self):
"""This method returns the expiration time."""
return self.jwt_expiration_secs
def create_token(self, subject: str) -> str:
"""This method creates a new JWT token."""
logger.info(f'Creating JWT token for subject {subject}')
now = datetime.now(timezone.utc)
payload = {
'sub': subject,
'iat': int(now.timestamp()),
'exp': int((now + timedelta(seconds=self.jwt_expiration_secs)).timestamp()),
'iss': 'polarsparc.com',
}
return jwt.encode(payload, self.jwt_secret_key, algorithm=self.jwt_algorithm)
def verify_token(self, token: str) -> str:
"""This method verifies a JWT token."""
logger.info(f'Verifying JWT token')
subject = None
try:
payload = jwt.decode(token, self.jwt_secret_key, algorithms=[self.jwt_algorithm])
subject = payload.get('sub')
except JWTError as e:
logger.error(f'JWT error: {e}')
return subject
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='token')
jwt_manager = JoseJwtManager()
async def get_subject(token: str = Depends(oauth2_scheme)):
subject = jwt_manager.verify_token(token)
if subject is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Could not validate token credentials',
headers={'WWW-Authenticate': 'Bearer'},
)
return {'subject': subject}
app = FastAPI()
@app.post('/token')
async def login(credentials: Credentials):
logger.info(f'Username: {credentials.username}')
user_credentials = {
'ada_l@mail.com': 'Ada.S3cr3t',
'dennis.ritchie@att.com': 'Dennis.C0d3',
'james_g@sun.com': 'James.P4ss',
}
if credentials.username in user_credentials:
secret = user_credentials[credentials.username]
if credentials.password == secret:
token = jwt_manager.create_token(credentials.username)
return {'access_token': token, 'token_type': 'bearer'}
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Incorrect username or password',
headers={'WWW-Authenticate': 'Bearer'},
)
@app.get('/greet')
async def greet():
return {'message': f'Howdy buddy, welcome to the unrestricted zone !!!'}
@app.get('/protected/greet')
async def protected_greet(payload: dict = Depends(get_subject)):
return {'message': f'Howdy {payload['subject']}, welcome to the protect zone !!!'}
def main():
logger.info('Starting web server on port 8000')
uvicorn.run(app, host='192.168.1.25', port=8000)
if __name__ == '__main__':
main()
To execute the above Python code, execute the following command in a terminal window:
$ python sample_7.py
The output would be similar to that of Output.1 from above.
Open a terminal window and execute the following command:
$ echo '{"username": "James", "password": "dog"}' | http POST http://192.168.1.25:8000/token
The following would be the typical output:
HTTP/1.1 401 Unauthorized
content-length: 43
content-type: application/json
date: Mon, 06 Oct 2025 00:46:39 GMT
server: uvicorn
www-authenticate: Bearer
{
"detail": "Incorrect username or password"
}
In the same terminal window, now execute the following command:
$ echo '{"username": "james_g@sun.com", "password": "James.P4ss"}' | http POST http://192.168.1.25:8000/token
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 232
content-type: application/json
date: Mon, 06 Oct 2025 00:48:24 GMT
server: uvicorn
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqYW1lc19nQHN1bi5jb20iLCJpYXQiOjE3NTk3MTE3MDUsImV4cCI6MTc1OTcxMTc5NSwiaXNzIjoicG9sYXJzcGFyYy5jb20ifQ.NOnNlv8i20_IEPSMOMEi7WjD1xngRblX2R6FEuDKXok",
"token_type": "bearer"
}
Using the access token from above, in the same terminal window, now execute the following command:
$ http -A bearer -a "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqYW1lc19nQHN1bi5jb20iLCJpYXQiOjE3NTk3MTE3MDUsImV4cCI6MTc1OTcxMTc5NSwiaXNzIjoicG9sYXJzcGFyYy5jb20ifQ.NOnNlv8i20_IEPSMOMEi7WjD1xngRblX2R6FEuDKXok" http://192.168.1.25:8000/protected/greet
The following would be the typical output:
HTTP/1.1 200 OK
content-length: 68
content-type: application/json
date: Mon, 06 Oct 2025 00:49:02 GMT
server: uvicorn
{
"message": "Howdy james_g@sun.com, welcome to the protect zone !!!"
}
Wait a few seconds and retry the above command in the same terminal window one more time.
The following would be the typical output:
HTTP/1.1 401 Unauthorized
content-length: 49
content-type: application/json
date: Mon, 06 Oct 2025 00:52:46 GMT
server: uvicorn
www-authenticate: Bearer
{
"detail": "Could not validate token credentials"
}
With this, we conclude the various demonstrations on using the FastAPI web framework for building and deploying REST based API services !!!
References