DEV Community

Cover image for Flask Rest API -Part:2- Better Structure with Blueprint and Flask-restful
Paurakh Sharma Humagain
Paurakh Sharma Humagain

Posted on • Edited on

Flask Rest API -Part:2- Better Structure with Blueprint and Flask-restful

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)

Collapse
 
shwetankverma profile image
Shwetank Verma

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.

Collapse
 
paurakhsharma profile image
Paurakh Sharma Humagain

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 😊

Collapse
 
shwetankverma profile image
Shwetank Verma • Edited

Thank you so much Paurakh.. Good luck :-)
Eagerly waiting for the article :-)

Collapse
 
rjgazarek profile image
RJ Gazarek

hmmm so did you setup a blueprint, and then in the next part undo that?

Collapse
 
paurakhsharma profile image
Paurakh Sharma Humagain • Edited

Yes, thats right :)
We could have used both Blueprint and Flask-restful together.
But that would be overkill for this simple application.

Collapse
 
julsperez profile image
Julio PΓ©rez HernΓ‘ndez

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!

Collapse
 
paurakhsharma profile image
Paurakh Sharma Humagain

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 😊

Collapse
 
paurakhsharma profile image
Paurakh Sharma Humagain • Edited

I am glad that you liked it ☺️
Thank you for the suggestion. I will add a part on authentication
πŸ˜„

Collapse
 
ccollosi profile image
TheNephilim

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!

Collapse
 
henriquepboueri_99 profile image
henriquepboueri

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 :)