- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Guided: Building a REST API in Python
In this lab, you will build a Python-based REST API service to manage products. You'll learn to define data models, configure the FastAPI environment, and implement core REST operations such as creating, reading, updating, and deleting products. Additionally, you'll explore best practices for designing and testing APIs.
Lab Info
Table of Contents
-
Challenge
Welcome to the lab
Welcome to the lab
Guided: Building a REST API in Python
Introduction
Welcome to the lab on building REST APIs with Python! In this lab, you'll learn the fundamentals of creating RESTful APIs, a key component in modern web development.
REST APIs allow software systems to communicate with each other over the web, enabling seamless integration and data exchange.
Core REST API
In this lab, you'll implement RESTful principles by creating an API service designed to manage products. This service will allow users to perform operations such as adding new products, retrieving existing products, updating product details, and deleting products. The core CRUD (Create, Read, Update, Delete) operations, which are the backbone of REST APIs.
Testing with OpenAPI Documentation
You'll leverage FastAPI's integrated Swagger UI to test the API during development. This powerful tool simplifies the process of designing, documenting, and validating REST APIs, ensuring your endpoints are well-documented and function as expected.
By the end of this lab, you'll have the foundational knowledge and skills to build and test REST APIs effectively.
If you encounter any challenges, feel free to consult the
/solutiondirectory for guidance.To begin, click the right-arrow below.
-
Challenge
Define Validation Schema
Define Validation Schema
To start, define the schema that will act as the foundation of the project.
This schema will outline the structure of the data you will manage, ensuring consistency and validation throughout the API.
Using Pydantic
Ensuring data validation and consistency is crucial when developing APIs. Pydantic, a robust Python library, simplifies this process by allowing you to define input and output data structures using type annotations.
For this project, you will define a
Productschema based on Pydantic'sBaseModel.To accomplish this, open the
/schema.pyfile and add the following definition:class ProductCreate(BaseModel): name: str description: str price: Decimal class ProductRead(ProductCreate): id: intCreate and Read Classes
The
ProductCreateclass extends Pydantic'sBaseModel, enabling data validation and serialization.The
idfield is dynamically generated when a new product is created. Therefore, the create product endpoint should not require anidfield.All the other endpoints deal with existing products, which need to be managed by a product
idfield. This requirement is handled by theProductReadclass, which extendsProductCreateby adding anidfield.This design allows for creating products without an
idand retrieving products with anid.Adding Constraints
To ensure data integrity, Pydantic provides various validators that can be used to enforce these constraints. Validators are functions that check whether the data meets specific criteria.
Pydantic enforces type validation by default and ensures that every declared field has a value. However, you can configure additional validation rules as needed.
To see this in action, scroll to the bottom of the
schema.pyfile to the main guard block. Here, you will find an example of how to create aProductCreateinstance though currently thepricefield is commented out.You can test the validation, by running this file with the following command:
python schema.pyWhen you run the script, Pydantic will raise a
ValidationError, with a message that theField required .... This demonstrates how Pydantic automatically validates data types based on the defined schema.You could uncomment the
pricefield and run the script again. This time, you will see a different error message indicating that theInput should be a valid decimal.Only when a valid
pricevalue is provided, will the instance be created successfully.Other Validators
Currently, this schema does not prevent a negative price, which is unsuitable for a product. To address this, you can use Pydantic's
Fieldmethod to define constraints and metadata.To achieve this, refactor the
pricefield to be defined by Pydantic'sFieldfunction, which allows you to specify additional validation rules:price: Decimal = Field(..., gt=0)Now if you set the
priceto a negative value, Pydantic will raise aValidationError, indicating that theInput should be greater than 0.Pydantic offers a wide range of validators, making it a valuable tool for enforcing rules on submitted data. Exploring these validators further can enhance your understanding and ensure robust data validation.
With the
Productschema defined and basic constraints in place, you are now ready to proceed with declaring the data storage.Click the right arrow below to continue.
-
Challenge
The API Environment
The API Environment
With the schema declared, you can begin setting up the API environment. This involves creating a FastAPI application and configuring it to handle incoming requests.
Using the FastAPI framework
FastAPI is a modern web framework for building APIs with Python. It is designed to be fast, easy to use, and highly performant.
Installing Libraries
----- The required libraries for this project are pre-installed in this environment. If you are setting up a different environment, you can install FastAPI and Uvicorn (an ASGI server) using pip: ```bash pip install fastapi uvicorn ```Open the
/app.pyfile to review the basic setup of a FastAPI application. This serves as the foundation for developing your API.Notice the predefined root endpoint decorated with
@app.get("/"). You'll use this endpoint to test the API and ensure it's running correctly.Running the FastAPI application
You can use the Uvicorn server to start and run this FastAPI application.
What is Uvicorn
----- Uvicorn is an ASGI server used to run FastAPI applications.ASGI (Asynchronous Server Gateway Interface) is a specification for asynchronous communication between Python web servers and applications, enabling high-performance and scalable web apps.
Opening the
/main.pyfile, you will find the call touvicorn.run(). This function is responsible for starting the server and running the FastAPI application.Reload Parameter
----- Notice that the `reload` parameter is set to `True`. This enables the application to automatically reload when changes are made to the code, which is particularly useful during development.To launch the server, run the following command in your terminal:
python main.pyYou should see output indicating that the server is running.
Reviewing the API
Once the API is running, navigate to the
Simple Browsertab and click the circular reload icon next to the address bar. This url opens the/docspath which is the default location for the Swagger UI documentation generated by FastAPI.External Browser
-----If you prefer to view the API documentation in a separate browser tab, click the icon with a box and an arrow located to the right of the address bar.
OpenAPI Documentation
FastAPI generates OpenAPI documentation automatically, which is displayed in this Swagger UI. This documentation offers a comprehensive view of your API's endpoints, request parameters, and response formats, along with the ability to test endpoints directly within the interface.
To test an endpoint, expand its row by clicking on it. Next, click the
Try it outbutton, provide any necessary parameters, and pressExecute. This will send a request to the API and display the response directly within the documentation interface. A successful 200 response should return a JSON body containing themessagefield.Now that the API is launched, you can begin building out your endpoints and implementing the necessary functionality.
Proceed by clicking the right arrow below.
-
Challenge
Simulating a Database
Simulating a Database
Interacting with a database is a common task in web development, and FastAPI provides excellent support for this. However, setting up a full database can be time-consuming and complex, especially during the early stages of development.
In this project, you will simulate a database using an in-memory list to store your data. This approach allows you to focus on building your API without the overhead of managing a database.
This functionality has been provided for you in the
/services.pyfile.By separating this functionality into a temporary service layer, you can easily switch to a real database later without affecting the rest of your code. This modular approach is a best practice in software development, allowing for easier maintenance and scalability.
Service Layer
This service layer is designed to handle the CRUD operations for your products. It provides methods for creating, reading, updating, and deleting products from the in-memory list.
In the
/app.pyfile, you will find that theservicevariable is initialized with an instance of theProductServicesclass. This instance serves as the interface for interacting with the database throughout the API.With these supporting files in place, you are now ready to begin developing the API endpoints.
In the next several steps you will create the API routes that will handle incoming requests and interact with the service layer to perform the necessary operations.
-
Challenge
Creating Products
Creating Products
With the data schema and service layer in place, the next step is to implement the API endpoint for creating products.
Returning to the
/app.pyfile, scroll to just after the@app.get("/")root endpoint. This is where you will define the new endpoint for creating products.Design Endpoint
The create endpoint should receive a JSON payload adhering to the
ProductCreateschema and pass it to theservice.add_product()method for processing.Keep in mind that the
ProductCreateschema is utilized here because theidfield is not needed during product creation. Theidwill be generated and assigned by the service layer when the product is added to the database.Implementing the Endpoint
FastAPI endpoints are defined as functions decorated with the appropriate HTTP method.
To implement the create product endpoint, add the following function just below the root endpoint:
@app.post("/products/") async def create_product(product: ProductCreate): return service.add_product(product)Reviewing this code:
@app.post("/products/"):
This FastAPI decorator designates the function as an endpoint that processes POST requests directed to the/products/URL.async def:
This signifies that the function is asynchronous, enabling it to process requests without blocking the server. This is particularly beneficial for I/O-bound tasks such as database interactions.create_product(product: ProductCreate): This function takes a
ProductCreateobject as input, with FastAPI handling validation against the specified schema automatically.return service.add_product(product):
This forwards theProductCreateobject to theadd_product()method, which adds the product to the database. FastAPI then serializes the returned object into a JSON response.Testing the Endpoint
To test the
create_productendpoint, navigate to the Swagger UI documentation page and refresh it. This can be accessed via theSimple Browsertab or the external browser you opened earlier. You should now see thePOST /products/ Create Productendpoint listed in the API documentation.Expanding this endpoint will display the expected request body format, which corresponds to the
ProductCreateschema you defined earlier.You can verify the endpoint by clicking the
Try it outbutton and entering a JSON object in the request body, such as:{ "name": "Test Product", "description": "This is a test product.", "price": 19.99 }Next, click the
Executebutton to send a POST request to the/products/endpoint with the provided JSON payload.Upon a successful request, you will receive a response containing the created product object, including the
idand other details. This confirms that the product has been successfully added to the simulated database.Handling Errors
Error handling is a critical component of API design. FastAPI simplifies this by automatically validating request bodies against the defined schema.
For example, you can test this behavior by sending a request with an invalid JSON payload, such as:
{ "name": "Test Product", "description": "This is a test product.", "price": "not a number" }When this request is executed, FastAPI validates the input and responds with a detailed error message highlighting the invalid field, accompanied by a 422 Unprocessable Entity status code.
Improving Error Handling
Although FastAPI automatically manages validation errors, in a real-world application, you should also handle other potential issues, such as database connection failures or unexpected server errors. FastAPI's exception handling features can help you address these scenarios effectively.
You have completed your first endpoint for creating Products! In the next step, you will implement the endpoint for retrieving products.
-
Challenge
Fetch Products
Fetch Products
After creating a new product, the next step is to add functionality for retrieving products from the database. This involves implementing endpoints to fetch all products and to fetch a specific product by its ID.
Design Endpoint
To fetch all products, define a new endpoint that calls the
service.get_all_products()method. This will enable your application to return a complete list of products.Add a GET endpoint in the
/app.pyfile below thecreate_productfunction to retrieve all products:@app.get("/products/") async def get_products(): return service.get_all_products()This code is similar to the previous example, but here you are using a GET request to fetch data instead of a POST request to create it.
The
@app.get("/products/")decorator specifies that this function handles GET requests to the/products/URL.Testing the Endpoint
When you refresh the Swagger UI documentation page, the
GET /products/endpoint will appear.Test this endpoint by selecting the
Try it outbutton and then clickingExecute.The response body should initially display an empty list. This occurs because the server reloads and reinitializes the in-memory database with each update. To test with data, you will need to first call the create endpoint to add a product to the database.
Endpoint Summary
-----You may be wondering where the "Get Products" summary to the right of the
GET /products/endpoint comes from. This is automatically generated by FastAPI based on the function name and docstring.If you are interested in customizing the summary or description of your endpoints, you can do so by adding a docstring to the function. For example:
@app.get("/products/", summary="Get all products", description="Fetches a list of all products in the database." )If you make that change and refresh the Swagger UI documentation page, you will see the updated summary and description for the
GET /products/endpoint.GET Product by ID
To fetch details of a specific product using its
id, create an endpoint that acceptsproduct_idas a path parameter. This adheres to RESTful API design principles, allowing your application to retrieve detailed information about an individual product.Insert the following endpoint into the
app.pyfile directly below theget_productsfunction:@app.get("/products/{product_id}") async def get_product(product_id: int): return service.get_product_by_id(product_id)This endpoint is similar to the previous one, but now includes a path parameter
{product_id}. The parameter corresponds to theproduct_idargument in the function definition. FastAPI automatically extracts this parameter from the URL and passes it to the function.Testing the Endpoint
To test the
get_productendpoint, refresh the Swagger UI documentation page. You should now see theGET /products/{product_id}endpoint listed.Keep in mind that to test this functionality, you must first create a product using the
POST /products/endpoint and note down theidof the created product.After creating a product, test the
GET /products/{product_id}endpoint by expanding on the endpoint and clicking theTry it outbutton. Enter theidof the previously created product in theproduct_idfield and clickExecute.The response body should display the details of the specified product, confirming that the endpoint is functioning correctly.
Congratulations! You have successfully created and tested the endpoints for retrieving products. Next, you will implement functionality to update products.
-
Challenge
Update Products
Update Products
With the ability to create and retrieve products in place, the next step is to add functionality for updating existing products.
Design Endpoint
To modify a product, the
PUTHTTP method is used. This method is designed to update an existing resource on the server. Here, you will define an endpoint that enables updating a product's details using itsid.Add the following code to the
/app.pyfile, placing it directly below theget_productfunction:@app.put("/product/{product_id}") async def update_product(product_id: int, product: ProductCreate): product = service.update_product(product_id, product) return productBy now, the FastAPI decorators should be a familiar concept. This endpoint follows a similar structure to the ones implemented earlier.
The decorator defines the path as
/product/{product_id}and sets the HTTP method toPUT. Declaring theproduct_idas a path parameter.The function accepts two parameters:
product_id: which corresponds to the path parameter.product: which is of typeProductCreate. Note that this schema excludes theidfield since theproduct_idis already provided in the path.The function then calls the
service.update_product()method, which handles updating the product in the database.Testing the Endpoint
Keep in mind that to test this functionality, you must first create a product using the
POST /products/endpoint and note down theidof the created product.After refreshing the Swagger UI documentation page, you will see the newly added
PUT /product/{product_id} Update Productendpoint.By clicking the
Try it outbutton, you can provide theproduct_idpath parameter for the product you wish to update, along with the updated product details. Use the sameproduct_idthat you retrieved earlier to test the functionality.When you execute the request, the product will be updated in the database, and the response body will display the updated product details.
Enhancing the Endpoint
The current implementation of the
update_productendpoint is minimal and does not handle cases such as when the specified product does not exist or if an error occurs during the update process.For instance, if you try to update a product that does not exist, the current
service.update_product()implementation will returnNone. This results in a response with a200 OKstatus and a body ofnull, which is not helpful to the user. You can test this behavior by passing aproduct_idthat does not exist in the database, such as 0.To improve the user experience, you can modify the
update_productfunction to raise an HTTP exception when the product is not found. This ensures the client receives a more informative response.FastAPI provides the
HTTPExceptionclass, which allows you to return HTTP errors with a specific status code and a descriptive error message.You can refactor the
update_productfunction'sreturnstatement with the following:if product is None: raise HTTPException(status_code=404, detail="Product not found") return productIf the service layer cannot locate the product, the endpoint will now raise an HTTP exception with a
404 Not Foundstatus code and a descriptive message stating that the product was not found. This ensures the client receives a clearer and more informative response, indicating that the product they attempted to update does not exist.You can rerun the
PUT /product/{product_id}endpoint with a non-existentproduct_idto see this new error message.You are almost complete with the product management functionality. The last step is to implement the ability to delete products from the database.
-
Challenge
Delete Products
Delete Products
You are almost done with implementing the product management functionality. So far, you have implemented the ability to create, read, and update products. The final step is to add the functionality to delete products from the database.
Endpoint Design
In a REST API, the DELETE method is used to remove a specific resource identified by a unique identifier, such as
product_id. Upon successful deletion, the server should return an appropriate status code, like204 No Content, indicating the operation was successful with no additional content in the response.To implement this functionality, you will define a new endpoint that takes
product_idas a path parameter. This endpoint will invoke theservice.delete_product()method to delete the corresponding product from the database.Implementing the Endpoint
Add the following code to the
app.pyfile, placing it directly below theupdate_productfunction:@app.delete("/product/{product_id}", status_code=status.HTTP_204_NO_CONTENT ) async def delete_product(product_id: int): success = service.delete_product(product_id)This code is similar to the previous examples. It sets up an endpoint to handle DELETE requests at the
/product/{product_id}URL. Theproduct_idpath parameter is passed to theservice.delete_product()method, which deletes the specified product from the database.In this decorator, the
status_codeparameter is set to204 No Content, which is a standard RESTful API convention indicating that the deletion was successful and there is no content in the response body.You can enhance this endpoint further by using the
HTTPExceptionclass to handle scenarios where the specified product does not exist. This approach would be similar to how you handled such cases in theupdate_productendpoint.Summary
Well done on completing the product management functionality! You have now implemented the full CRUD operations—create, read, update, and delete—for managing products in your application.
This lab serves as an introduction to FastAPI's capabilities. To take your application to the next level, consider exploring advanced features like authentication, authorization, and middleware.
Here's to your success in mastering FastAPI and Python—keep up the great work!
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.