Marshmallow: From S'mores to Flask in Python


When working with server-side routing in web development, it is important to validate any data to prevent the application from falling apart. Just as marshmallows are one of the three crucial components in a s'more, they help with one of the three aspects of validation. There are three types of server-side validations: database level, model level, and app level.
Types of Validation
When validating data at the database level, SQLAlchemy allows us to set database constraints when defining columns in a model. A simple example:
# user.py (/models)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False, required=True)
In this very small example, we have a few constraints in each column definition. For the id column, we are making sure it is the primary key. In both username and password, we want to make sure the value passed is not Null and we want a unique username and a password to exist.
Additional database constraints can be found in the documentation linked.
When validating at the model level, we would import validates from SQLAlchemy ORM (Object Relational Mapper) and use the validates decorator to ensure that we have correct data before database operations. Here is an example of a decorator:
# user.py (/models)
class User(db.Model):
# ...
@validates('username')
def validate_username(self, key, value):
if not isinstance(value, str):
raise TypeError('Username must be a string')
elif len(value) < 2:
raise ValueError('Username must be at least 2 characters')
return value
This validator is applied whenever the username attribute is set after there is an entry in the database. First, our decorator @validates contains string(s). After creating a function name, we pass in 3 arguments: self, key (which is 'username'), and value (the new username we are trying to set). If the value is not a string, we raise an error and let the user know that the username must be a string. Then, if the length of the username is less than 2, we raise another error. Lastly, the most important part, we return the value once the validations pass.
Additional model constraints can be found in the documentation linked.
Note: Don't forget to return the value or else you will return 'Null' after your value has been validated.
Now we get to what this blog is mainly about. Marshmallow validations at the app level. When sending or receiving data to and from the frontend, we need to serialize, deserialize, AND validate the data. With a little bit of setup, Marshmallow takes care of all of this for us.
Marshmallow Setup Components
There are a few things we need to implement Marshmallow.
Import Marshmallow
Wrap our Flask app
Create a Schema to validate data
Serialize and deserialize with schema methods
Imports
# app_setup.py
from flask import Flask
from flask_marshmallow import Marshmallow
# Make sure to include capitalization
At the minimal level, we need to import Flask and Marshmallow, which is a Flask library.
Wrap
# app_setup.py
# Creates Flask application object
app = Flask(__name__)
# Wrap our app object
ma = Marshmallow(app)
Now that the app is set up, we can create a schema for the user class above.
Schema
# user_schema.py (/schemas)
from marshmallow import fields, validate
from models.user import User
from app_setup import ma
class UserSchema(ma.SQLAlchemySchema):
class Meta():
# Name of our model
model: User
# Loads model instance
load_instance = True
# Specifies which fields to serialize (Not deserialize)
fields = ['id', 'username']
username = fields.String(required=True, validate=validate.Length(min=2, max=20)
password = fields.String(required=True, validate=validate.Length(min=8, max=20)
There are 3 import statements here from the code blocks we used above. The import locations are based on a full-stack application I helped create called Virtual Planner.
We can see that similar validations are occurring but they are applied when data is transmitted between the front and backend of the application. I will expand on where these are happening in the next section.
Additional app level constraints can be found in the documentation linked.
For username, we make sure it is a string, it exists, and is a minimum of length 2 and a maximum of length 20. Same for the password.
Note: Passwords are not hashed in this blog
Serialization and Deserialization
Serialization is the process of converting an object into a string and deserialization does the opposite (string -> object). An easier way of understanding this is when you are creating a new account on a website and you want to save that data in a database. Here is an account registration example:
# register.py (/routes)
from . import request, session, Resource
from schemas.user_schema import UserSchema
from app_setup import db
# Define user_schema variable using our schema we created
user_schema = UserSchema(session=db.session)
class Register(Resource):
def post(self):
try:
# Get data from form
data = request.json
# Validate user information using our schema
user_schema.validate(data)
# Deserialize data to create new user using load method
new_user = user_schema.load(data)
# Add user to session and commit to database
db.session.add(new_user)
db.session.commit()
# Serialize data and package your JSON response
serialized_user = user_schema.dump(new_user)
# Return user JSON string with status code
return serialized_user, 201
# Catch any errors during our "try"
except Exception as e:
# Remove user IF added to session
db.session.rollback()
# Return an error with a message and status code
return {'error': str(e)}, 400
This class takes care of data persistence using Marshmallow when creating an account on a website. Let's go through the process.
When you press a "sign up" button, you will have to fill out a form with a username/email input box and a password input box. When you submit, our frontend fires a fetch request as a "POST" method and then our backend starts working. The correct route is checked and now we are within our post method defined.
First, we grab the user's information with request.json and we are given a dictionary with username and password as keys and the user's input as the values. That data is a JSON string that is then deserialized. Now that data is ready to be inserted into our database. Once the new account information is saved in the database, we use the dump method to serialize the user and return it to the frontend to display. Now, we have a response object with the JSON string and response code and we can apply methods to display the username to the application.
Marshmallow comes with these methods which simplifies the process and allows to serialize and deserialize data within the same method. Through Marshmallow, we can connect client and server-side data allowing data sent from the client to persist in the server and data received from the server to display without manually changing data types.
Conclusion
After getting experience creating a full-stack application, using Marshmallow helped me understand each level of validation and what type of data is being sent from the client and received from the server. It can seem like a lot of setting up but once you write your first schema, the following schemas are similar in structure.
If you would like to check out an application to plan out learning and practicing Marshmallow, check out the Virtual Planner repository!
Subscribe to my newsletter
Read articles from Isaac Song directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Isaac Song
Isaac Song
I am a student at Flatiron School in the software engineering program. I have a passion to learn and I hope to become the best developer I can be!