Flask + VueJS + MongoDB: Simple CRUD Application — Part I
What will we learn?
How to create a simple application in VueJS that will use flask as backend to demonstrate CRUD operations on a User model. In Part I of this series will cover topics related to creation of a flask backend application that connects with mongoDB and exposes CRUD operations as REST APIs.
Part I: Expose CRUD operations on User model as REST API using Flask.
Part II: User-friendly UI using Vue.js to talk to Flask backend.
Architecture
Code Structure
flask-vuejs-mongodb-crud│ .env
│ .gitignore
│ docker-compose-dev.yml
│ docker-compose.yml
│ README.md
│
├───nginx
│ Dockerfile
│ nginx.conf
│
├───client
│
├───server
│ app.py
│ config.py
│ errors.py
│ requirements.txt
│ Dockerfile
│
├───────db
│ models.py
│ __init__.py
│
└───────rest
routes.py
user.py
__init__.py
The folder named server under project root (flask-vuejs-mongodb-crud) holds the flask backend application and related files.
Configuration
MongoDB related values are set to variables prefixed with MONGODB_. The values are fetched from environment variables set in .env file.
Database(MongoDB) models and connection initialization
flask-mongoengine extension has been used for creating the integration between flask and mongo. The initialize_db() method will be later imported in app.py and used for initializing the DB connection on server startup. Logger has been added to the method to provide confirmation on server startup.
models.py inside db folders define a simple User model that has a name and email (defined to be unique to avoid multiple users with same email). User extends MongoEngine’s Document class to enable basic ORM type operation but on document database like mongoDB.
Restful API Resources
flask_restful extension has been used to create clean REST APIs that adhere to best practices and works well with ORM extensions
UsersApi resource will serve requests for fetching all available users using the get() method and create a single user using the post() method.
UserApi resource will server request for update user via put(), delete user via delete() and fetch a single user via get().
Custom error handling
We have placed custom error handling by implementing try/except on possible errors and raising custom errors with relevant messages for the client. For example, if the client tries to create a user with an existing email, MongoEngine will throw NotUniqueError (email field in User model has been defined to be unique). But this error doesn’t make up for a meaningful message and http status code for client. We have intercepted the exception in except block and raised a custom error that defines appropriate message and http status code.
#errors.py
from werkzeug.exceptions import HTTPExceptionclass InternalServerError(HTTPException):
passclass SchemaValidationError(HTTPException):
passclass UserNotFoundError(HTTPException):
passclass EmailAlreadyExistError(HTTPException):
passerrors = {
"InternalServerError": {
"message": "Oops something wrong",
"status": 500
},
"SchemaValidationError": {
"message": "Required fields missing",
"status": 400
},
"UserNotFoundError": {
"message": "User not found in database",
"status": 400
},
"EmailAlreadyExistError": {
"message": "User with specified email already exists in database",
"status": 400
},}
All custom error classes should extend HTTPexception class. The messages and status code are defined in errors dictionary which will be passed on to Api initializer later in app.py.
Routing requests
We learned how the requests are served and errors are handled, but how are the requests routed to these resources? That is done using the routes.py file in folder named rest.
#rest/routes.py
from .user import UsersApi, UserApidef initialize_routes(api):
api.add_resource(UsersApi, '/users')
api.add_resource(UserApi, '/users/<id>')
With rest resources defined in user.py and routing handled by routes.py, we need to define an initialization method that can be later called by app.py to initialize the rest endpoints with proper configuration. This will be done in __init__.py in rest folder.
#rest/__init__.py
from flask_restful import Api
from .routes import initialize_routesfrom errors import errorsapi = Nonedef initialize_api(app):
app.logger.info("Initializing REST Apis")
api = Api(app, errors=errors)
initialize_routes(api)
As in case of db initialization, logger has been added to ensure confirmation of the intialization.
Stitching all together in app.py!
We have kept the app.py simple by moving all extension (MongEngine and flask-restful) related code to respective modules and linked them to app.py via initialize_*() methods.
#app.py
import jsonfrom flask import Flaskfrom config import BaseConfig
from db import initialize_db
from rest import initialize_apiapp = Flask(__name__)
app.config.from_object(BaseConfig)initialize_db(app)initialize_api(app)if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0',port=8000)
Starting servers in development mode
As with my previous articles I have created a dev docker compose that will not only spin up mongoDB instance, but also run the flask application in development mode with hot reload!
Go to the root of the project (flask-vuejs-mongodb-crud) and execute
docker-compose -f docker-compose-dev.yml up –build
You should see the DB and REST initialization entries in logs along with others.
fms_dev_server | [2021-01-19 04:56:26,315] INFO in __init__: Initializing MongoDB
fms_dev_server | [2021-01-19 04:56:26,347] INFO in __init__: Initializing REST Apis
Time for testing
Create user
curl --location --request POST 'localhost:8000/users' \--header 'Content-Type: application/json' \--data-raw '{"name": "laura","email": "laura1@gmail.com"}'
Fetch all users
curl --location --request GET 'localhost:8000/users'
Go on, play around with other CRUD operation like update and delete :).
In part II of this series we shall create a client application in VueJS which will perform all CRUD operations on User model and handle error response with a appealing UI.
Github link of complete source code here.
Part II here.
Happy Coding!