Build a Serverless AI-Powered Python API
TL;DR: In this tutorial, you'll learn how to build and deploy a serverless backend in Python that integrates an AI text summarization feature - all using the open-source Nitric framework. We'll go from setting up a Nitric project, writing an API endpoint that calls an AI service, testing it locally (with a slick built-in dashboard), to deploying it on the cloud. No prior cloud knowledge or tedious infrastructure configuration required.
Modern backend development is exciting and fast-moving - serverless architectures and AI integrations are among 2025's hottest trends. Yet, if you're a Python developer with minimal cloud experience, diving into these topics can feel daunting. This guide is here to help. We'll leverage Nitric, a cloud-agnostic framework that abstracts away the cloud complexity, letting you focus on code, not servers or YAML config. By the end, you'll have a working Python API that takes in text and returns an AI-generated summary, deployed on a cloud provider.
Why Nitric? (and What We're Building)
Nitric is an open-source framework that acts as a bridge between your code and cloud services, taking care of provisioning resources (APIs, queues, databases, etc.) and wiring up permissions behind the scenes. It's multi-language and multi-cloud, meaning you can write in Python (or TypeScript, Go, etc.) and deploy to AWS, Azure, or GCP without changing your code. This saves tons of boilerplate and avoids cloud vendor lock-in.
What we'll build: a RESTful API (using Nitric) with a single endpoint that accepts a block of text and returns a concise summary generated by an AI. We'll use OpenAI's API for the text summarization and by the end, you can deploy it and hit a live endpoint that summarizes text for you. Along the way, you'll see how Nitric simplifies common tasks (like local testing, resource provisioning, and deployment).
Key Nitric benefits we'll see in action:
- Developer-friendly: Feels familiar if you've used web frameworks (you'll define routes and handlers in Python). No need to write Terraform or juggle AWS consoles - Nitric infers the necessary cloud resources from your code.
- Accelerated development: Built-in local development server and dashboard for instant testing and debugging. You can run your serverless app locally with one command and even see a visual API explorer.
- Portability: Nitric lets you deploy anywhere - AWS, GCP, Azure - by simply switching a setting, with all cloud-specific details handled for you. We'll deploy to AWS in this tutorial, but you could choose another cloud with minimal changes.
- Minimal configuration: The framework uses an “infrastructure from code” approach. We won't write separate infrastructure files; our Python code (and a tiny project config) is enough for Nitric to provision what's needed.
Now, let's get our tools ready and dive in.
Prerequisites
Before we begin, make sure you have the following:
- Python 3.11+ installed on your system (the latest 3.x is fine). Basic familiarity with Python is assumed.
- Nitric CLI installed - this is the command-line tool to create, run, and deploy Nitric projects. You can install it by following the installation instructions.
- uv or Pipenv for managing Python dependencies. The Nitric Python templates use either uv or Pipenv.
- An OpenAI API key if you want to integrate with OpenAI's GPT for summarization.
- An AWS account (or Google Cloud or Azure account) with credentials configured on your machine, if you plan to deploy to the cloud. Nitric will use these credentials to provision resources. (For AWS, having the AWS CLI configured or environment vars set for access keys is enough.)
You can deploy to any of the three major clouds - Nitric supports all. In this tutorial we'll mention AWS for concreteness, but feel free to use your preferred cloud.
Project Setup - Your First Nitric App
Nitric projects have a simple structure: your code (services, functions, etc.) lives in a directory (by default called services/
), and a nitric.yaml
config file describes high-level settings (like which files are services and what runtime to use). We'll use the Nitric CLI to scaffold a new project:
- Create a new Nitric project: Open a terminal and run:
nitric new my-summarizer py-starter
This will create a new folder my-summarizer
with a Python starter template. (You can choose a different name for your project; py-starter
selects the Python starter template.) If you omit the template, the CLI will interactively ask for a language/template. The CLI's new
command guides you through project creation and offers ready-made templates.
If none of the templates suit your needs, you can always create your own.
- Install dependencies: Navigate into the project and install the requirements. The template uses Pipenv, so you can do:
cd my-summarizeruv sync
This will install Nitric's Python SDK (and any other deps). If you used a different template, change this step to install dependencies accordingly.
- Explore the scaffold: The generated project should have a structure like:
my-summarizer/├── services/│ └── api.py├── nitric.yaml├── pyproject.toml├── python.dockerfile├── python.dockerfile.dockerignore└── README.md
The services/api.py
is a sample service that Nitric created (a simple “Hello, World” HTTP endpoint). We'll replace this with our own code. The nitric.yaml
config by default tells Nitric to treat any *.py
in services/
as a service and what runtime to use. We'll keep the config mostly default.
- (Optional) Verify the template works: You can test the starter project right away by running
nitric start
inside the project. This spins up Nitric's local development server. Check the terminal output - you should see that an API endpoint is available locally at some port (like 4001). For now, stop the server (Ctrl+C) - we're going to write our own service next.
With the project set up, let's start coding our API.
Writing the Python API Service (with AI Integration)
Our goal is to create an HTTP endpoint /summarize
(for example) that accepts some text and returns a summary of that text. We'll implement this as an API service in Nitric. In Nitric's model, an API can have multiple RESTful routes, served by one or more services. We define an API and then use decorators to attach function handlers to specific HTTP methods and paths - very similar to frameworks like Flask or FastAPI, but with Nitric's @api
decorators.
Steps:
-
Create a new service file: In the
services/
directory, create a filesummarize.py
(you can name it differently, but let's go with this for clarity). -
Import Nitric and dependencies: We'll need Nitric's API utilities and the OpenAI library (for AI). Make sure you add
openai
to your dependencies (uv add openai
). Then, insummarize.py
:
import osfrom openai import OpenAIfrom nitric.resources import apifrom nitric.application import Nitricfrom nitric.context import HttpContext
Here we import api
to create an API resource, Nitric
to run the app, and HttpContext
for typing (the context object passed into handlers). We'll use os
to read the OpenAI API key from an environment variable, and openai
to call the OpenAI service.
- Configure OpenAI API key: It's best not to hardcode secrets. If you have your OpenAI API key, set it as an environment variable (e.g., create a .env file for the project containing
OPENAI_API_KEY=sk-...
). We can then configure the OpenAI client in code:
# Configure OpenAI API key from envapi_key = os.environ.get("OPENAI_API_KEY")if not api_key:raise Exception("Please set the OPENAI_API_KEY environment variable")
This will fetch the API key from your environment. (If you prefer, Nitric also has a built-in Secrets feature to securely manage secrets, but for simplicity we'll stick to an env var here.)
- Create the OpenAI client: Now, use Nitric to create a POST route for summarization:
# Create OpenAI clientclient = OpenAI(api_key=api_key)
- Define the API and route handler: Now, use Nitric to create a POST route for summarization:
# Create a Nitric API named 'main'summarize_api = api("main")@summarize_api.post("/summarize")async def summarize_text(ctx: HttpContext) -> None:# Extract input text from request (assuming JSON body with a 'text' field)req_data = ctx.req.jsonif not req_data or 'text' not in req_data:ctx.res.status = 400ctx.res.body = {"error": "No text provided for summarization."}returntext_to_summarize = req_data['text']# Call OpenAI API to get summary (this is a blocking call, for demo simplicity)try:response = client.chat.completions.create(model="gpt-3.5-turbo",messages=[{"role": "user", "content": f"Summarize the following text:\n\n{text_to_summarize}"}],max_tokens=50)# Extract the assistant's reply (summary text)summary = response.choices[0].message.content# Set responsectx.res.body = {"summary": summary.strip()}except Exception as e:ctx.res.status = 500ctx.res.body = {"error": str(e)}Nitric.run()
Let's break down what's happening:
- We created an API service named
"main"
. (Nitric uses this name to group routes; you can have multiple APIs. The default template also uses"main"
for the primary API, which is why we chose it here.) - We define a POST route at path
/summarize
using a decorator@summarize_api.post("/summarize")
. This means our functionsummarize_text
will handle HTTP POST requests to/summarize
. - In the handler, we access
ctx.req.json
- Nitric conveniently parses JSON request bodies for us intoctx.req.json
. We expect the client to send JSON like{"text": "some long text to summarize"}
. If the JSON or field is missing, we return a 400 Bad Request with an error message. - We then call the OpenAI API (using the ChatGPT model
gpt-3.5-turbo
) with a prompt asking to summarize the input text. We limit themax_tokens
for the summary to ~100 tokens (roughly a few sentences) to keep it concise. - The OpenAI response contains a list of choices; we extract the content of the first choice (which is the model's summary text).
- We set
ctx.res.body
to a JSON object containing the summary. Nitric will serialize this to JSON for the HTTP response. By default, returning a Python dict asres.body
results in a JSON response. - If the OpenAI API call fails (e.g., network issue or invalid key), we catch the exception and return a 500 error with the message.
A note on async: We defined the handler as async def
. The Nitric framework supports async handlers, which is great for I/O operations. The OpenAI Python SDK call we used (ChatCompletion.create
) is a blocking call. In a real app, you might integrate an async HTTP client or use OpenAI's async methods (if available) to avoid blocking the event loop. However, for simplicity in this tutorial, this is acceptable - our focus is on the integration, not optimizing async usage.
- Finalize the Nitric app run: After defining all your routes and handlers in the file, make sure to add:
Nitric.run()
This call tells Nitric to start the application (it will scan for all defined services/routes and run the local server when invoked via nitric start
). In our case, it picks up summarize_api
and the attached route.
That's it for our service code! We have a complete Python backend that will accept text and return an AI-generated summary. The entire summarize.py
file should look like this (for reference):
import osfrom openai import OpenAIfrom nitric.resources import apifrom nitric.application import Nitricfrom nitric.context import HttpContext# Configure OpenAI API key from envapi_key = os.environ.get("OPENAI_API_KEY")if not api_key:raise Exception("Please set the OPENAI_API_KEY environment variable")# Create OpenAI clientclient = OpenAI(api_key=api_key)# Create a Nitric API named 'main'summarize_api = api("main")@summarize_api.post("/summarize")async def summarize_text(ctx: HttpContext) -> None:# Extract input text from request (assuming JSON body with a 'text' field)req_data = ctx.req.jsonif not req_data or 'text' not in req_data:ctx.res.status = 400ctx.res.body = {"error": "No text provided for summarization."}returntext_to_summarize = req_data['text']# Call OpenAI API to get summary (this is a blocking call, for demo simplicity)try:response = client.chat.completions.create(model="gpt-3.5-turbo",messages=[{"role": "user", "content": f"Summarize the following text:\n\n{text_to_summarize}"}],max_tokens=50)# Extract the assistant's reply (summary text)summary = response.choices[0].message.content# Set responsectx.res.body = {"summary": summary.strip()}except Exception as e:ctx.res.status = 500ctx.res.body = {"error": str(e)}Nitric.run()
With our code ready, let's test it out locally before deploying.
Testing Locally with Nitric
One of Nitric's superpowers is its local development experience. You can run your cloud app locally, including any cloud resources it defines, using the CLI. Nitric will simulate the cloud services and even provide a local web dashboard where you can interact with your API (no need for Postman or separate testing tools).
Make sure you've saved your summarize.py
. Also ensure the old api.py
(from the template) is either removed or its content is replaced - we only want one service running (our new one). Now, run:
nitric start
This command will compile and start your Nitric application locally. By default, Nitric will assign a local port for your API (usually 4001 for the first API service). In the console output, look for a line that indicates the API endpoint and port (e.g., "api:main - http://localhost:4001").
Once the local server is up, two ways to test:
A. Using the Nitric Local Dashboard: The CLI should automatically open a browser window for the Nitric dashboard (or you can navigate to the URL it shows, typically localhost:49152
for the dashboard UI). On the left, you'll see your APIs and their routes. You should see an API (named “main”) with a POST endpoint /summarize
. You can click that and use the interface to provide input and send a request.
After sending a request with some text, you should see the response at the bottom of the dashboard. If all went well, it will contain a JSON with a summary. (For example, summarizing a paragraph about Nitric might return something like {"summary": "Nitric is a framework that simplifies cloud app development by handling infrastructure from code."}
)
You can also check the console logs where nitric start
is running to see any log output or errors. If the OpenAI call fails (e.g., because of a missing/invalid API key), you'll see the error stack trace there.
B. Using cURL or another HTTP client: Alternatively, you can test without the dashboard. Since the app is running on localhost (say port 4001), you can use a command-line HTTP request. For example:
curl -X POST http://localhost:4001/summarize \-H "Content-Type: application/json" \-d '{"text": "Nitric is an open-source framework that handles cloud infrastructure from code, making it easier to build and deploy applications."}'
This should return a JSON response with a summary. You might see output like:
{"summary": "Nitric is an open-source framework that simplifies building and deploying cloud applications by handling the cloud infrastructure details for developers."}
Feel free to experiment with different input texts. Because we're using a general AI model, it should be able to summarize most coherent English text. Keep the input relatively short in these tests to avoid hitting token limits.
Local testing is super fast - you can edit your Python code, save, and Nitric auto-reloads the changes (the dashboard and local server update on the fly). This tight feedback loop means you can iterate on your function logic quickly. Once you're satisfied with local results, it's time to deploy our app to the cloud 🚀.
Deploying to the Cloud
Deploying a serverless app usually involves setting up cloud services (like an API gateway, a function, IAM permissions, etc.) which can be a lot of work manually. Nitric automates all of this for us. With a couple of CLI commands, Nitric will provision the necessary resources on your chosen cloud provider and get our API running live.
1. Create a deployable stack: In Nitric, a stack is basically a deployment environment (combining your app code with cloud config like region and provider). We need to create one before our first deploy. Run:
nitric stack new prod-stack
(You can name the stack as you like, e.g., “dev-stack” or “prod-stack”.)
The CLI will prompt you a cloud provider (choose “AWS” for this tutorial, or GCP/Azure if you prefer),
This command creates a stack configuration file (for example, prod-stack.yaml
) in your project. Open this file - you'll see it lists the chosen provider, an empty region key, and some default config for how to deploy your services. Nitric has already made best-practice choices (like naming conventions, runtime settings, etc.) which you can tweak if needed. Ensure the region is set correctly for your account.
2. Deploy the stack: Now the magic moment. Run:
nitric up
This tells Nitric to deploy (“up”) the stack we just created. The CLI will now translate your application into cloud resources and deploy them. Under the hood, Nitric uses cloud IaC providers (like Pulumi or Terraform) to create the infra from your code definitions, but you don't have to write any of that yourself. It will automatically set up everything: an HTTP endpoint (API Gateway or equivalent), a cloud function to run your summarize_text
code, and any required permissions (IAM roles, etc.).
You'll see a progress log in the console. The first deployment may take a few minutes as it's creating cloud resources. Once complete, Nitric will output the endpoint URL for your API. It might look something like:
https://xxxxxx.execute-api.us-east-1.amazonaws.com
(The exact format depends on the cloud - for AWS, it uses API Gateway; for others, a similar endpoint will be provided.
3. Test the live endpoint: Time to test our deployed function on the cloud! You can use curl
again, but now replace the localhost URL with the one given by Nitric. For example:
curl -X POST https://<<your-api-url>>/summarize \-H "Content-Type: application/json" \-d '{"text": "OpenAI API integration with serverless functions is very powerful, allowing applications to leverage AI without managing infrastructure."}'
(Make sure to put your actual endpoint URL in place of <<your-api-url>>
.
You should get a JSON response with a summary, similar to what you saw locally. Congratulations - your AI-powered summarization service is now live on the internet, running serverlessly! 🎉
A few things to appreciate here:
- We did not manually configure any AWS Lambda, API Gateway, IAM role, etc. Nitric handled all that configuration for us based on our code. When we ran
nitric up
, it knew we defined an API with a POST route, so it provisioned the necessary API endpoint and a function to handle it, plus the permissions (for example, allowing the function to make outbound network calls, etc.). In traditional setups, you might have written Terraform or clicked around the console for each of those pieces. Nitric delivered it in one shot. - If you wanted to deploy the same app to another cloud, you could create another stack (say
nitric stack new gcp-stack
and select GCP) and runnitric up -s gcp-stack
. Your code wouldn't need any changes - multi-cloud deployment without code changes is a core benefit of Nitric. - The endpoint is secure and ready to use. You could integrate it with a frontend application (imagine a simple web form that calls this API and displays the summary) or share it with others to use (just be mindful of your OpenAI API usage, since that can incur cost).
Important: Remember that our cloud function still needs the OpenAI API key. How did that work on deploy? If you deployed to AWS, Nitric likely packaged your local environment variable into the cloud configuration. Nitric will include any values in a .env file present at deploy time, by default, but there are many other options for dealing with secrets and config.
Wrap Up and Next Steps
In this tutorial, we built a serverless Python API that integrates an AI service, and we did it without wrestling with cloud infrastructure. Here's a quick recap of what we achieved:
- Used Nitric to scaffold a project and avoid boilerplate. We wrote just one Python file for our logic; Nitric inferred the rest.
- Implemented an API endpoint with Python async syntax, leveraging Nitric's simple decorators to define routes.
- Integrated an AI API (OpenAI) to perform text summarization - demonstrating how easy it is to plug in powerful third-party services in a serverless function.
- Tested locally with Nitric's built-in dashboard and CLI (
nitric start
), getting instant feedback without deploying to the cloud for each change. - Deployed to the cloud with a couple of commands. Nitric provisioned the necessary cloud resources and gave us a live endpoint, all without us writing any infrastructure code. The framework “understood” our application's needs and handled the heavy lifting.
All of this was done without needing to be AWS experts or know how to set up API Gateway, etc. Nitric's philosophy is to let developers focus on app logic while it ensures the cloud plumbing is correct and optimal. This means faster development cycles and less chance of misconfigurations. For example, Nitric set up IAM roles with only the permissions our app needed, reducing security risks by default.
Where to go from here? The possibilities are vast:
- You can extend this project by adding more endpoints. For instance, you could add a
/sentiment
endpoint that uses a different AI model or library to do sentiment analysis on text. Simply define another route with@summarize_api.post("/sentiment")
in the same file or a new service file - Nitric will incorporate it and you can deploy the update. - Add other Nitric resources: need a database or key-value store to cache results? Nitric offers
sql()
andkv()
resources that can be used similarly to how we defined the API. Want to trigger actions on a schedule (cron jobs) or handle file storage in the cloud? Nitric has primitives for schedules, buckets (object storage), queues, pub-sub topics, and more - all accessible through simple code constructs, and all cloud-agnostic. (For example, you could schedule a daily job to fetch some data and summarize it, using Nitric'sschedule
resource.) - Deploy to another cloud: Try deploying the same app to Azure or GCP. It should be as easy as creating a new stack for that provider. This can be a great way to learn differences in cloud offerings without changing your code - Nitric will map your API to the equivalent service (e.g., Azure Container Apps + API Management, or GCP Cloud Run + API Gateway).
As you continue exploring, check out the official Nitric documentation and examples for deeper dives. The Nitric docs site has guides on all resource types and features (for instance, how to use Queues/Topics for event-driven patterns, or how to secure your API with auth). There's also a community Discord and more examples on GitHub if you run into questions.
We hope this tutorial showed you that modern cloud development doesn't have to be intimidating. Using Nitric, you offloaded the undifferentiated heavy lifting of cloud setup and got straight to building something useful. Happy coding, and may all your deployments be this smooth! 🚀