Part 2: Better Structure with Blueprint and Flask-restful
Howdy! In the previous Part of the series, we learned how to use Mongoengine
to store our movies data into a MongoDB database. Now let's learn how you can structure your flask application in a more maintainable way.
If you are just starting from this part, you can find all the code we wrote till now here.
We are going to learn two ways of structuring the flask application:
Blueprint: It is used to structure the Flask application into different components, making structuring the application based on different functionality.
Flask-restful: It is an extension for Flask that helps your build REST APIs quickly and following best practices.
Note: Blueprint
and Flask-restful
are not a replacement for each other, they can co-exist in a single project
Structuring Flask App using Blueprint
Create a new folder resources
inside mongo-bag
and a new file movie.py
inside resources.
mkdir resources
cd resources
touch movie.py
Now move all the route related codes from your app.py
into movies.py
#~/movie-bag/resources/movie.py
@app.route('/movies')
def get_movies():
movies = Movie.objects().to_json()
return Response(movies, mimetype="application/json", status=200)
@app.route('/movies', methods=['POST'])
def add_movie():
body = request.get_json()
movie = Movie(**body).save()
id = movie.id
return {'id': str(id)}, 200
@app.route('/movies/<id>', methods=['PUT'])
def update_movie(id):
body = request.get_json()
Movie.objects.get(id=id).update(**body)
return '', 200
@app.route('/movies/<id>', methods=['DELETE'])
def delete_movie(id):
movie = Movie.objects.get(id=id).delete()
return '', 200
@app.route('/movies/<id>')
def get_movie(id):
movies = Movie.objects.get(id=id).to_json()
return Response(movies, mimetype="application/json", status=200)
And you app.py file should look like this
#~/movie-bag/app.py
from flask import Flask, request, Response
from database.db import initialize_db
from database.models import Movie
import json
app = Flask(__name__)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
}
initialize_db(app)
app.run()
Clean right?
Now you might be wondering how does this work? - This doesn't work.
We must first create a blueprint in our movie.py
Update your movie.py
like so,
#~/movie-bag/resources/movie.py
+from flask import Blueprint, Response, request
+from database.models import Movie
+
+movies = Blueprint('movies', __name__)
-@app.route('/movies')
+@movies.route('/movies')
def get_movies():
movies = Movie.objects().to_json()
return Response(movies, mimetype="application/json", status=200)
-@app.route('/movies', methods=['POST'])
+@movies.route('/movies', methods=['POST'])
def add_movie():
body = request.get_json()
movie = Movie(**body).save()
id = movie.id
return {'id': str(id)}, 200
-@app.route('/movies/<id>', methods=['PUT'])
+@movies.route('/movies/<id>', methods=['PUT'])
def update_movie(id):
body = request.get_json()
Movie.objects.get(id=id).update(**body)
return '', 200
-@app.route('/movies/<id>', methods=['DELETE'])
+@movies.route('/movies/<id>', methods=['DELETE'])
def delete_movie(id):
movie = Movie.objects.get(id=id).delete()
return '', 200
-@app.route('/movies/<id>')
+@movies.route('/movies/<id>')
def get_movie(id):
movies = Movie.objects.get(id=id).to_json()
return Response(movies, mimetype="application/json", status=200)
So, we have created a new Blueprint
using
+movies = Blueprint('movies', __name__)
with arguments name
and import_name
. Usually, import_name will just be __name__
, which is a special Python variable containing the name of the current module.
Now we can replace every instance of app
inside this Blueprint
with movies
.
So, let's update our app.py
to register the Blueprint
we created.
#~/movie-bag/app.py
-from flask import Flask, request, Response
+from flask import Flask
from database.db import initialize_db
-from database.models import Movie
-import json
+from resources.movie import movies
app = Flask(__name__)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
}
initialize_db(app)
+app.register_blueprint(movies)
app.run()
That's it, you have used Blueprint
to structure your Flask
application. And it looks way cleaner than it was before.
Structuring Flask REST API using Flask-restful
Now, let's get to the main topic we have been waiting for from the beginning.
Installing flask-restful
pipenv install flask-restful
Now, let's update our movie.py
to use flask-restful
#~movie-bag/resources/movie.py
-from flask import Blueprint, Response, request
+from flask import Response, request
from database.models import Movie
+from flask_restful import Resource
+
-movies = Blueprint('movies', __name__)
+class MoviesApi(Resource):
+ def get(self):
+ movies = Movie.objects().to_json()
+ return Response(movies, mimetype="application/json", status=200)
+
+ def post(self):
+ body = request.get_json()
+ movie = Movie(**body).save()
+ id = movie.id
+ return {'id': str(id)}, 200
+
+class MovieApi(Resource):
+ def put(self, id):
+ body = request.get_json()
+ Movie.objects.get(id=id).update(**body)
+ return '', 200
+
+ def delete(self, id):
+ movie = Movie.objects.get(id=id).delete()
+ return '', 200
+
+ def get(self, id):
+ movies = Movie.objects.get(id=id).to_json()
+ return Response(movies, mimetype="application/json", status=200)
+
-@movies.route('/')
-def get_movies():
- movies = Movie.objects().to_json()
- return Response(movies, mimetype="application/json", status=200)
-
-@movies.route('/', methods=['POST'])
-def add_movie():
- body = request.get_json()
- movie = Movie(**body).save()
- id = movie.id
- return {'id': str(id)}, 200
-
-@movies.route('/<id>', methods=['PUT'])
-def update_movie(id):
- body = request.get_json()
- Movie.objects.get(id=id).update(**body)
- return '', 200
-
-@movies.route('/<id>', methods=['DELETE'])
-def delete_movie(id):
- movie = Movie.objects.get(id=id).delete()
- return '', 200
-
-@movies.route('/<id>')
-def get_movie(id):
- movies = Movie.objects.get(id=id).to_json()
- return Response(movies, mimetype="application/json", status=200)
As we can see flask-restful
uses a Class-based syntex so, if we want to define a resource (i.e API) we can just define a class which extends flask-restful
's Resource
i.e
+class MoviesApi(Resource):
+ def get(self):
+ movies = Movie.objects().to_json()
+ return Response(movies, mimetype="application/json", status=200)
This creates an endpoint which accepts GET
request.
Now let's register these endpoints that we just created.
Let's create a new file routes.py
inside resources
directory and add the following to it.
#~movie-bag/resources/routes.py
from .movie import MoviesApi, MovieApi
def initialize_routes(api):
api.add_resource(MoviesApi, '/movies')
api.add_resource(MovieApi, '/movies/<id>')
We have defined the function to initialize the routes. Let's call this function from our app.py
#~/movie-bag/app.py
-from resources.movie import movies
+from flask_restful import Api
+from resources.routes import initialize_routes
app = Flask(__name__)
+api = Api(app)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
}
initialize_db(app)
-app.register_blueprint(movies)
-
+initialize_routes(api)
app.run()
Here we first created an Api
instance with app = Api(app)
and then initialized the API routes with initialize_routes(api)
Wow! we did it y'all. Now we can access our movies at http://localhost:5000/movies
. As we can see just by looking at the URL we cannot know if this is an API. So, let's update our routes.py
to add api/
in front of our API routes.
#~movie-bag/resources/routes.py
- api.add_resource(MoviesApi, '/movies')
- api.add_resource(MovieApi, '/movies/<id>')
+ api.add_resource(MoviesApi, '/api/movies')
+ api.add_resource(MovieApi, '/api/movies/<id>')
Now we can access our movies at http://localhost:5000/api/movies
.
You can find the complete code of this part here
What we learned from this part of the series?
- How to structure our Flask App using
Blueprint
- How to create REST APIs using
Flask-restful
In the next part, we are going to learn how to implement
Authentication
and Authorization
in our REST API.
Until then happy coding π
Top comments (10)
I am new to flask. This post helped me have better understanding of things along with hands on.
Instead of Flask-restful, can't we use Flask-restplus. As Flask-restplus comes with swagger-ui, it helps us to have proper visualization of our API.
Kindly help in achieving the same.
I came across few posts online with respect to Flask-restplus, but being novice in this topic found it bit complex.
I am glad this article helped you π
Regarding your question, I haven't used Flask-restplus, I will have a look at it and try to come up with an article for it as well. Thanks for the suggestion π
Thank you so much Paurakh.. Good luck :-)
Eagerly waiting for the article :-)
hmmm so did you setup a blueprint, and then in the next part undo that?
Yes, thats right :)
We could have used both Blueprint and Flask-restful together.
But that would be overkill for this simple application.
Hello, thank you for your knowledge.
I have followed every step in this tutorial and I found great but a little bit confusing.
First of all, when I try to get the movies using the endpoint '/api/movies', the result is only '[]'. I think is because we never populate the database, isn't it?
How can I get the list of movies that we use in the part 0 and 1?
Also, I guess that is missing the port in app.py
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost:27017/movie_bag'
}
In other hand, I suppose that a requirement to this series is have installed mongodb locally.
When you show the code that we need to delete (red) and add (green) you didn't show the entire code so I had to go to your repository and check the complete changes.
Thank you in advance!
Thank you for the comments.
The result is only
[]
probably because you do not have any movies in the DB.By default, the port is
27017
so I didn't include it. (But I should have mentioned in the post) - Thank you for bringing this up.I mentioned in the previous part of the series to install MongoDB with respective links for each OS.
Yeah about that red and green part. If you followed along with the tutorial you don't have to touch any other codes that are not indicated by the green or red color. If you are just starting with this part you have to first clone the project from the first article GitHub link.
But I should include it at the beginning of each part as well.
Thank you for all the suggestions. These suggestions make me do better for my next articles.
Happy Coding π
I am glad that you liked it βΊοΈ
Thank you for the suggestion. I will add a part on authentication
π
This is fantastic so far. I used docker-compose to spin up a mongodb docker image and it worked just fine. Looking forward to the next part!
I cannot find enough words to express how much this series of articles is helping me.
Thank you so much, and keep up the good job.
Your work really makes a difference :)