Create a basic REST API in Python with Flask
NicolasBrondinBernard
Learn all the basics of Flask, by building a basic API to understand the mechanics of the framework!

Article published on 04/06/2025, last updated on 10/08/2026
In this tutorial, we're going to learn step by step how to create a REST API from scratch in Python with the Flask framework. This API will allow you to build a very simplified user management system, in order to better understand the mechanics and syntax of Flask.
We won't be using a database, all data will be managed in Python, to keep the tutorial simple.
Prerequisites
In order to properly follow and understand the rest of the content, you first need to:
- Know the basics of Python
- Understand the basics of the web (web server, HTTP requests,…)
- Know what a REST API is
To follow this tutorial under the best conditions, also make sure that Python is installed on your machine with a version ≥ 3.11.1
Installation
Flask is a web framework for Python, it can be used to create websites/web applications, APIs, and generally anything that requires listening to HTTP requests and being able to respond to them.
First, we're going to install Flask, using the pip package manager:
$> pip install Flask
Once done, create a folder and give it a name, to house our API project. For example:
mon-api-flask
My first API
In your project folder, create a main.py file, and paste in the following code:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello, World!"
Here's what this code does:
- We initialize the Flask app, giving it the name of the module (here the __ name __ variable contains "main" because the name of our file is
main.py) - We declare a new route at the root (
/), on which we'll listen for all HTTP GET requests (by default) on the defined url - We define the controller logic of our route, like a classic function
- We return information (here a string) in the HTTP response, which will come with a 200 code (by default)
To test this example, you need to run our API, passing the name of our application, with the run command:
$> flask --app main run
You should see a message saying:
Serving Flask app 'main'which means your API is running!
To test it, simply open the url http://127.0.0.1:5000 in your browser, and if you see "Hello World" appear, then the first step is successful!
Returning JSON
There's a good chance that you'd like your API to not just return plain text, but to be able to return objects in JSON format!
And that's great, because Flask makes this task much easier for us:
@app.route("/json")
def hello_json():
return {"str1": "Hello", "str2": "world"}
# or return ["hello", "world"]
And that's it! "Key-value" dictionaries (as well as lists) are automatically serialized to JSON by Flask.
Returning objects (classes)
Things get slightly more complicated when we want to return actual objects (meaning "class instances").
And yet, this is essential, because it's good practice when creating an API!
This allows:
- Data validation (and keeping certain information hidden)
- Keeping the same data model as in the rest of your application
- Not having to go back through a key-value dictionary for each object
Let's take a simple fictional class:
class User:
username: str
firstname: str
lastname: str
password: str
def __init__(self, username: str, firstname: str, lastname: str):
self.username = username
self.firstname = firstname
self.lastname = lastname
self.password = "azejbnzdfunIJIN65"
We can't directly turn its instances into JSON, because Python doesn't know how to do it from a complex object.
So we're going to use a data serialization/deserialization and validation library called Marshmallow
$> pip install marshmallow
And we're going to create a validator for our User class, so that marshmallow understands how to go from our User class to a JSON object, and vice-versa:
from marshmallow import Schema, fields
# [...]
class UserSchema(Schema):
username = fields.Str(required=True)
firstname = fields.Str(required=True)
lastname = fields.Str(required=True)
# We create the validator from our schema
user_validator = UserSchema()
You'll have noticed that the password attribute is not present, because we never want to return it outside of our API… It's a safeguard!
Now let's add a new route to return our user, created using our User class, and transformed by marshmallow:
users = []
users.append(User("JohnDoe", "John", "Doe"))
# [...]
# description: returns the first user in the list
@app.route("/user")
def get_user():
first_user = users[0]
# dump(...) allows converting a class instance to json
response = user_validator.dump(first_user)
return response
Open http://127.0.0.1:5000/users and admire the result: your user, properly formatted (and without a password)!
Handling the different HTTP methods
An API isn't just used to retrieve data, but also to manage it, store it…
To follow the structure of a Rest API, we use the different HTTP methods (Get, Post, Put, Delete)
Flask lets us do this easily, directly in the route definition with the methods parameter:
# description: returns the list of all users
@app.route("/users", methods=['GET'])
def get_users():
return "not implemented"
# description: adds a user to the list
@app.route("/users", methods=['POST'])
def create_user():
return "not implemented"
The possible values are
'GET','POST','PUT'and'DELETE'
Receiving data
Now that we know how to return all sorts of data in our API's responses, we need to learn how to retrieve the data received in requests!
There are two main ways to pass data in an HTTP request:
- In the URL's parameters
- In the request's body
In the request parameters
For URL parameters, it's very simple, you just need to read request.args:
from flask import Flask, request #new import (request)
# [...]
@app.route("/hello") # Example /hello?name=John
def hello_name():
name = request.args.get("name")
return "Hello, " + name
In the request body
It's also possible to retrieve data sent as JSON in the request body for POST and PUT methods.
By default, the JSON content will be turned into a key-based array, and accessible like this:
@app.route("/users", methods=['POST'])
def create_user():
json_data = request.get_json()
# json_data = { "username": "...", "firstname": "...", "lastname": "..."}
Deserializing JSON
Just as serializing a class instance to send it as an API response, retrieving the request body to store it in an object is a good practice!
And thanks to the
marshmallowlibrary, we'll be able to store the request content in an object (and validate the data) very simply, without installing anything more:
# description: adds a user to the list
@app.route("/users", methods=['POST'])
def create_user():
json_data = request.get_json()
try:
user = user_validator.load(json_data)
if user:
users.append(user)
return "User created", 201
except Exception as err:
return err.__str__(), 400
Thanks to our user_validator, an exception will be thrown if the object received as input doesn't match the expected schema (and will return a 400 error)!
Creating a dynamic route
To define a route that will take one or more parameters in its URL, you can define each parameter in the form <type:name>, like this:
# description : Returns the user matching the id
@app.route("/users/<int:id>")
def get_user_by_id(id: int) :
user = users[id]
return user_validator.dump(user)
Try it yourself: http://127.0.0.1:5000/users/0
Note that we could also have passed a string, such as a username, like this: /users/<String:username>
Bonus: creating a DTO
If you're not familiar with the concept of Data Transfer Object, you can read our article on the subject: https://code-garage.fr/blog/what-are-data-transfer-objects-dto-used-for/
In short, a DTO is a validator that will only be used to validate the content of an object that travels over the network (here, the API response). It's exactly the same validation concept as before, except that we'll have a class dedicated to this response:
class GetUsersResponse:
def __init__(self, users):
self.users = users
self.count = len(users)
class GetUsersResponseDTO(Schema):
users = fields.List(fields.Nested(UserSchema()))
count = fields.Integer()
get_users_response_validator = GetUsersResponseDTO()
# description: returns the list of all users
@app.route("/users")
def get_users() :
response = GetUsersResponse(users);
return get_users_response_validator.dump(response)
This also lets you see how to validate a list of objects with Marshmallow, using fields.List(fields.Nested(UserSchema()))!
The complete code
Find the complete code for this tutorial below:
from flask import Flask, request
from marshmallow import Schema, fields
app = Flask(__name__)
### Classes
class User:
username: str
firstname: str
lastname: str
password: str
def __init__(self, username: str, firstname: str, lastname: str):
self.username = username
self.firstname = firstname
self.lastname = lastname
self.password = "azejbnzdfunIJIN65" #randomly generated
class UserSchema(Schema):
username = fields.Str(required=True)
firstname = fields.Str(required=True)
lastname = fields.Str(required=True)
# We create the validator from our schema
user_validator = UserSchema()
### Data
users = []
users.append(User("JohnDoe", "John", "Doe"))
### Examples
@app.route("/")
def hello_world():
return "Hello, World!"
@app.route("/json")
def hello_json():
return {"str1": "Hello", "str2": "world"}
# or return ["hello", "world"]
@app.route("/hello") # Example /hello?name=John
def hello_name():
name = request.args.get("name")
return "Hello, " + name
### Routes
# description: returns the first user in the list
@app.route("/user")
def get_user():
first_user = users[0]
# dump(...) allows converting a class instance to json
response = user_validator.dump(first_user)
return response
# description: adds a user to the list
@app.route("/users", methods=['POST'])
def create_user():
json_data = request.get_json()
try:
user = user_validator.load(json_data)
if user:
users.append(user)
return "User created", 201
except Exception as err:
return err.__str__(), 400
# description : Returns the user matching the id
@app.route("/users/<int:id>")
def get_user_by_id(id: int) :
user = users[id]
return user_validator.dump(user)
class GetUsersResponse:
def __init__(self, users):
self.users = users
self.count = len(users)
class GetUsersResponseDTO(Schema):
users = fields.List(fields.Nested(UserSchema()))
count = fields.Integer()
get_users_response_validator = GetUsersResponseDTO()
# description: returns the list of all users
@app.route("/users")
def get_users() :
response = GetUsersResponse(users);
return get_users_response_validator.dump(response)
Complete courses, exercises and certificates to really learn programming!
4.8 average rating
No comments yet