REST APIs are now a standard method for creating interfaces between the front-end and back-end of web applications, as well as between different web services. The widespread adoption of HTTP and HTTPS protocols across various networks and frameworks, combined with the simplicity of this interface style, makes it a natural choice when addressing interoperability concerns.
Bottle is a Python web framework known for its minimalist design. Its lightweight nature, speed, and ease of use make it well-suited for constructing RESTful services. In a bare-bones comparison conducted by Andriy Kornatskyy, it was ranked among the top three frameworks for response time and throughput (measured in requests per second). My own tests, conducted on DigitalOcean’s virtual servers, revealed that the combination of the uWSGI server stack and Bottle could achieve an overhead as low as 140μs per request.
This article provides a step-by-step guide on how to create a RESTful API service using Bottle.

Setting Up and Configuring Bottle
The impressive performance of the Bottle framework is partly attributed to its lightweight design. The entire library is packaged as a single-file module. While this means it offers less hand-holding compared to other frameworks, it also provides greater flexibility, allowing it to integrate into diverse technology stacks. This makes Bottle an excellent choice for projects that prioritize performance and customizability, and where the time-saving advantages of more feature-rich frameworks are less critical.
Given Bottle’s flexibility, a detailed explanation of setting up the platform might not be relevant to your specific stack. However, let’s provide a brief overview of the available options and where to find more information on configuring them:
Installing Bottle
Installing Bottle is as simple as installing any other Python package. You have the following options:
- Install it system-wide using your system’s package manager. For example, Debian Jessie (current stable release) offers version 0.12 as python-bottle.
- Install it system-wide using the Python Package Index with the command
pip install bottle. - Install it within a virtual environment (recommended).
To install Bottle in a virtual environment, you’ll need the virtualenv and pip tools. Installation instructions for these tools can be found in the virtualenv and pip documentation, although they are likely already present on your system.
In Bash, you can create an environment with Python 3 using the following command:
| |
Note that omitting the -p `which python3` parameter will result in the installation defaulting to the system’s default Python interpreter, which is typically Python 2.7. Although Python 2.7 is supported, this tutorial assumes you are using Python 3.4.
Once the environment is set up, activate it and proceed with the Bottle installation:
| |
That’s it! Bottle is now installed and ready for use. If you’re new to virtualenv or pip, their documentation is excellent. They are well worth exploring.
Choosing a Server
Bottle adheres to Python’s standard Web Server Gateway Interface (WSGI), making it compatible with any WSGI-compliant server. Some popular options include uWSGI, Tornado, Gunicorn, Apache, Amazon Beanstalk, Google App Engine, and more.
The exact setup process may differ slightly depending on the chosen environment. Bottle provides an object that conforms to the WSGI interface, and your server needs to be configured to interact with this object.
For more details on configuring your specific server, refer to the server’s documentation and Bottle’s documentation on here.
Database Considerations
Bottle is database-agnostic, meaning it doesn’t impose any restrictions on where your data resides. If you plan to use a database in your application, the Python Package Index offers a range of compelling options. These include libraries like SQLAlchemy, PyMongo, MongoEngine, and CouchDB for interacting with various databases, as well as Boto for working with DynamoDB. To get started, you’ll only need the appropriate adapter for your chosen database.
Getting Started with the Bottle Framework
Now, let’s dive into creating a basic application using Bottle. The code examples provided will assume you are using Python 3.4 or later. However, most of the concepts discussed should also apply to Python 2.7.
Here’s what a basic Bottle application looks like:
| |
When we say “basic”, we truly mean it. This program doesn’t even greet you with a “Hello World”. Think about it, when was the last time a REST interface responded with “Hello World?” In this basic setup, any HTTP request sent to 127.0.0.1:8000 will receive a 404 Not Found response status.
Understanding Bottle Apps
While you can create multiple instances of Bottle apps, the framework provides a convenient default instance for you. Bottle maintains these instances internally in a stack. When you interact with Bottle, such as running the app or defining a route, without specifying a specific app instance, it defaults to this default app. In fact, the line app = application = bottle.default_app() is not strictly necessary in this example. However, it’s included here to demonstrate how to explicitly reference the default app when working with servers like Gunicorn, uWSGI, or other generic WSGI servers.
The concept of multiple apps might seem a bit puzzling initially, but it adds to Bottle’s flexibility. For different parts of your application, you can create specialized Bottle apps by instantiating other Bottle classes and configuring them as needed. These separate apps could be accessed through distinct URLs, thanks to Bottle’s URL routing mechanism. While we won’t delve into that in this tutorial, you’re encouraged to explore Bottle’s documentation on here and here for more in-depth information.
Running Your Bottle Application
The final line of the script is responsible for running your Bottle application using the specified server. If you don’t specify a server, as in this case, Bottle defaults to Python’s built-in WSGI reference server, which is suitable for development purposes but not recommended for production.
You can use a different server by modifying the last line as follows:
| |
This provides a convenient way to start your app by simply running the script. For instance, if you saved this code as main.py, you can run python main.py to start the app using the specified server. Bottle provides several quite an extensive list of server adapters that can be used in this manner.
For WSGI servers that don’t have a specific Bottle adapter, you can start them using their own run commands. For instance, if you are using uWSGI, you would start it like this:
| |
Organizing Your Project Structure
Bottle doesn’t impose a strict file structure for your application, giving you the freedom to organize it as you see fit. While my approach tends to evolve from one project to the next, I often find myself leaning towards an MVC-inspired structure.
Creating Your First REST API with Bottle
Now, let’s move beyond simply returning 404 errors and build a real REST API.
Imagine you want to create an interface to manage a collection of names. In a real-world scenario, you’d likely store these names in a database. For the sake of this example, we’ll keep things simple and use Python’s built-in set data structure.
Here’s a basic outline for our API. You can place this code anywhere within your project, but I recommend creating a dedicated API file, such as api/names.py.
| |
Defining API Routes
As you can see, Bottle leverages decorators for routing. The imported decorators (post, get, put, and delete) are used to register handler functions for their corresponding HTTP actions. Let’s break down how they work:
- Each decorator is a shortcut to the corresponding routing decorator of the
default_appinstance. For instance,@get('/')is equivalent tobottle.default_app().get('/'). - The routing methods on the
default_appinstance are themselves shortcuts for the more generalroute()method. This meansdefault_app().get('/')is equivalent todefault_app().route(method='GET', '/').
Therefore, you can use @get('/'), @route(method='GET', '/'), and @bottle.default_app().route(method='GET', '/') interchangeably.
The @route decorator provides additional flexibility. For example, if you want to handle both object updates and deletions with the same handler function, you can pass a list of methods:
| |
Now, let’s move on to implementing some of these handler functions.

Handling POST Requests for Resource Creation
Here’s how you might implement the POST handler for creating new names:
| |
and:
| |
In our example, we opted for the more concise form. However, the second form allows you to provide a custom text description for the error. Bottle internally splits the string and sets the numeric status code accordingly.
Generating a Success Response
If all checks pass, we add the name to our _names set, set the Content-Type response header, and return a response. Any string returned by the handler function is treated as the body of a 200 Success response. We use json.dumps to serialize our data into a JSON string.
Handling GET Requests for Resource Listing
Let’s move on to the name listing handler, which is much simpler:
| |
Listing the names is straightforward. We set the appropriate response headers and return a JSON representation of all names in our set.
Handling PUT Requests for Resource Updates
Now, let’s explore how to implement the update method. While similar to the create method, it introduces the concept of URI parameters.
| |
The body schema for the update action remains the same as the creation action. However, we now have an oldname parameter in the URI, as specified by the @put('/names/<oldname>') route.
Working with URI Parameters
Bottle makes working with URI parameters intuitive. You can define routes with as many parameters as needed, and Bottle will automatically extract their values from the URI and pass them to your handler function:
| |
You can create routes with optional parameters using cascading route decorators:
| |
Bottle also supports various routing filters to enforce constraints on URI parameters:
int
Ensures that the parameter can be converted to an integer and passes the converted value to your handler:
1 2 3@get('/<param:int>') def handler(param): pass
float
Similar to
int, but for floating-point values:
1 2 3@get('/<param:float>') def handler(param): pass
re(regular expressions)
Matches only if the parameter conforms to the provided regular expression:
1 2 3@get('/<param:re:^[a-z]+$>') def handler(param): pass
path
Offers a flexible way to match subsegments of the URI path:
1 2 3@get('/<param:path>/id>') def handler(param): passFor example, it can match both
/x/id(passing “x” asparam) and/x/y/id(passing “x/y” asparam).
Handling DELETE Requests for Resource Deletion
The DELETE method is equally straightforward, much like the GET method. It’s worth noting that returning None without explicitly setting a status code will result in a response with an empty body and a 200 status code.
| |
Activating the API
Assuming you’ve saved your names API logic in a file named api/names.py, you can now activate these routes within your main application file, main.py.
| |
Note that we only import the names module here. This is because we’ve decorated all our handler functions with their respective routes and attached them to the default Bottle app. There’s no need for any further setup – our API methods are now accessible.

You can test the API manually using tools like Curl or Postman. If you’re using Curl, consider piping the output through a JSON formatter to enhance readability.
Bonus: Enabling Cross-Origin Resource Sharing (CORS)
One of the primary motivations for building REST APIs is to facilitate communication with JavaScript front-ends via AJAX. In certain scenarios, you might need to allow these requests to originate from any domain, not just the domain hosting your API. Most browsers, by default, restrict this behavior for security reasons. Here’s how you can configure cross-origin resource sharing (CORS) in Bottle to enable this:
| |
The hook decorator allows us to execute a function before or after each request. In this case, to enable CORS, we need to set the Access-Control-Allow-Origin, -Allow-Methods, and -Allow-Headers headers for every response. These headers inform the client about the allowed origins, methods, and headers for cross-origin requests.
Clients may also send an OPTIONS HTTP request to the server to inquire about permissible request methods. This snippet provides a catch-all example, responding to all OPTIONS requests with a 200 status code and an empty body.
To activate this CORS configuration, save it to a file and import it from your main module.
Conclusion
That concludes our whirlwind tour of building a REST API with Bottle.
We’ve covered the essential steps for creating a basic REST API for a Python application using the Bottle web framework.
For those eager to delve deeper into this compact yet powerful framework, be sure to explore the official Bottle tutorial and check out additional resources like API reference docs.
)
@post(’/names’) def creation_handler(): ‘‘‘Handles name creation’’’
try:
# parse input data
try:
data = request.json()
except:
raise ValueError
if data is None:
raise ValueError
# extract and validate name
try:
if namepattern.match(data['name']) is None:
raise ValueError
name = data['name']
except (TypeError, KeyError):
raise ValueError
# check for existence
if name in _names:
raise KeyError
except ValueError:
# if bad request data, return 400 Bad Request
response.status = 400
return
except KeyError:
# if name already exists, return 409 Conflict
response.status = 409
return
# add name
_names.add(name)
# return 200 Success
response.headers['Content-Type'] = 'application/json'
return json.dumps({'name': name})
| |
and:
| |
In our example, we opted for the more concise form. However, the second form allows you to provide a custom text description for the error. Bottle internally splits the string and sets the numeric status code accordingly.
Generating a Success Response
If all checks pass, we add the name to our _names set, set the Content-Type response header, and return a response. Any string returned by the handler function is treated as the body of a 200 Success response. We use json.dumps to serialize our data into a JSON string.
Handling GET Requests for Resource Listing
Let’s move on to the name listing handler, which is much simpler:
| |
Listing the names is straightforward. We set the appropriate response headers and return a JSON representation of all names in our set.
Handling PUT Requests for Resource Updates
Now, let’s explore how to implement the update method. While similar to the create method, it introduces the concept of URI parameters.
| |
The body schema for the update action remains the same as the creation action. However, we now have an oldname parameter in the URI, as specified by the @put('/names/<oldname>') route.
Working with URI Parameters
Bottle makes working with URI parameters intuitive. You can define routes with as many parameters as needed, and Bottle will automatically extract their values from the URI and pass them to your handler function:
| |
You can create routes with optional parameters using cascading route decorators:
| |
Bottle also supports various routing filters to enforce constraints on URI parameters:
int
Ensures that the parameter can be converted to an integer and passes the converted value to your handler:
1 2 3@get('/<param:int>') def handler(param): pass
float
Similar to
int, but for floating-point values:
1 2 3@get('/<param:float>') def handler(param): pass
re(regular expressions)
Matches only if the parameter conforms to the provided regular expression:
1 2 3@get('/<param:re:^[a-z]+$>') def handler(param): pass
path
Offers a flexible way to match subsegments of the URI path:
1 2 3@get('/<param:path>/id>') def handler(param): passFor example, it can match both
/x/id(passing “x” asparam) and/x/y/id(passing “x/y” asparam).
Handling DELETE Requests for Resource Deletion
The DELETE method is equally straightforward, much like the GET method. It’s worth noting that returning None without explicitly setting a status code will result in a response with an empty body and a 200 status code.
| |
Activating the API
Assuming you’ve saved your names API logic in a file named api/names.py, you can now activate these routes within your main application file, main.py.
| |
Note that we only import the names module here. This is because we’ve decorated all our handler functions with their respective routes and attached them to the default Bottle app. There’s no need for any further setup – our API methods are now accessible.

You can test the API manually using tools like Curl or Postman. If you’re using Curl, consider piping the output through a JSON formatter to enhance readability.
Bonus: Enabling Cross-Origin Resource Sharing (CORS)
One of the primary motivations for building REST APIs is to facilitate communication with JavaScript front-ends via AJAX. In certain scenarios, you might need to allow these requests to originate from any domain, not just the domain hosting your API. Most browsers, by default, restrict this behavior for security reasons. Here’s how you can configure cross-origin resource sharing (CORS) in Bottle to enable this:
| |
The hook decorator allows us to execute a function before or after each request. In this case, to enable CORS, we need to set the Access-Control-Allow-Origin, -Allow-Methods, and -Allow-Headers headers for every response. These headers inform the client about the allowed origins, methods, and headers for cross-origin requests.
Clients may also send an OPTIONS HTTP request to the server to inquire about permissible request methods. This snippet provides a catch-all example, responding to all OPTIONS requests with a 200 status code and an empty body.
To activate this CORS configuration, save it to a file and import it from your main module.
Conclusion
That concludes our whirlwind tour of building a REST API with Bottle.
We’ve covered the essential steps for creating a basic REST API for a Python application using the Bottle web framework.
For those eager to delve deeper into this compact yet powerful framework, be sure to explore the official Bottle tutorial and check out additional resources like API reference docs.