What is RESTful API?
Representational State Transfer that allows clients to interact with a server by performing HTTP operation (GET, POST, PATCH, DELETE) on resources.
In our project our resources are Convention Areas, Conventions, Host companies.
In our project we create and define the models for the resources so that we can have a database that will be the foundation of our API resources.
class ConventionArea(db.Model, SerializerMixin):
__tablename__ = 'convention_areas'
id = db.Column(db.Integer, primary_key=True)
location_name = db.Column(db.String, nullable=False)
venue = db.Column(db.String, nullable=False)
conventions = db.relationship('Convention', backref='convention_area', cascade='all, delete-orphan')
host_companies = association_proxy('conventions', 'host_company')
serialize_rules = ('-conventions.convention_area',)
class HostCompany(db.Model, SerializerMixin):
__tablename__ = 'host_companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
industry = db.Column(db.String, nullable=False)
conventions = db.relationship('Convention', backref='host_company')
convention_names = association_proxy('conventions', 'convention_name')
serialize_rules = ('-conventions.host_company',)
@validates('name')
def validate_name(self, key, name):
if not name:
raise ValueError('HostCompany name cannot be empty')
return name
@validates('industry')
def validate_industry(self, key, industry):
if not industry:
raise ValueError('Industry cannot be empty')
return industry
class Convention(db.Model, SerializerMixin):
__tablename__ = 'conventions'
id = db.Column(db.Integer, primary_key=True)
convention_name = db.Column(db.String, nullable=False)
days = db.Column(db.Integer, nullable=False)
convention_area_id = db.Column(db.Integer, db.ForeignKey('convention_areas.id'))
host_company_id = db.Column(db.Integer, db.ForeignKey('host_companies.id'))
serialize_rules = ('-convention_area.conventions', '-host_company.conventions')
host_company = association_proxy('host_company_id', 'host_company')
@validates('days')
def validate_days(self, key, days):
if not isinstance(days, int):
raise ValueError('Days must be an integer')
return days
The key component for our Resource Code are:
- CRUD Operations: Each resource class implements methods for Create, Read, Update, Delete operations.
- Endpoints: This defines the routes and the corresponding resource classes
- Serialization: Convert database objects to JSON format using the to_dict method
Here is the breakdown of each resource:
ConventionAreas Resource:
-> This class manages the convention areas and the places where the conventions are being held
class ConventionAreas(Resource):
def get(self):
areas = ConventionArea.query.all()
return [area.to_dict(only=('id', 'location_name', 'venue')) for area in areas]
def post(self):
data = request.get_json()
try:
new_area = ConventionArea(
location_name=data['location_name'],
venue=data['venue']
)
db.session.add(new_area)
db.session.commit()
return make_response(new_area.to_dict(), 200)
except ValueError as e:
print('Error', e)
return make_response({'errors': ['validation errors']}, 400)
api.add_resource(ConventionAreas, '/convention_areas')
GET: Retrieves all convention areas from the database and returns a list of dictionaries
-Process:
>Query the database to get all convention areas.
>Convert each convention area to a dictionary format(to_dict)
>Return the list of convention areas as JSON
POST: Creates a new convention area from the provided JSON data and saves it to the database
-Process:
>Retrieve the JSON data from the request
>Create a new ConventionArea object using the data
>Add the new object to the database and commit the transaction
>Return the new convention area as JSON
ConventionAreasById Resource:
-> this handle request for specific convention areas, we need to use this resource to to get the id parameter of the ConventionAreas.
class ConventionAreasById(Resource):
def get(self, id):
area = db.session.get(ConventionArea, id)
if area:
return area.to_dict(), 200
return {'error': 'ConventionArea not found'}, 404
def patch(self, id):
area = ConventionArea.query.filter_by(id=id).first()
if area:
try:
data = request.get_json()
for attr, value in data.items():
setattr(area, attr, value)
db.session.add(area)
db.session.commit()
return make_response(area.to_dict(), 202)
except ValueError:
return make_response({'errors': ['validation errors']}, 400)
else:
return make_response(jsonify({'error': 'ConventionArea not found'}), 404)
def delete(self, id):
area = db.session.get(ConventionArea, id)
if area:
db.session.delete(area)
db.session.commit()
return '', 204
return {'error': 'ConventionArea not found'}, 404
api.add_resource(ConventionAreasById, '/convention_areas/<int:id>')
GET: Fetches a specific convention area by its ID.
-Process:
>Query the database to get the convention area by ID
>Return the convention area as JSON
PATCH: Updates an existing convention area.
-Process:
>Retrieve the JSON data from the request
>Update the convention area with the new data
>Commmit the changes to the database
>Return the updated convention area as JSON
DELETE: Deletes a convention area by its ID.
-Process:
>Query the database to get the convention area by Id
>Delete the convention area from the database
>Commit the changes
Hosts Resource:
-> This resource manages the companies that host conventions
class Hosts(Resource):
def get(self):
convention_id = request.args.get('convention_id', type=int)
if (convention_id):
conventions = Convention.query.filter_by(id=convention_id).all()
hosts = [convention.host_company for convention in conventions if convention.host_company is not None]
else:
hosts = HostCompany.query.all()
return [host.to_dict(only=('id', 'name', 'industry')) for host in hosts]
def post(self):
data = request.get_json()
try:
new_host = HostCompany(
name=data['name'],
industry=data['industry']
)
db.session.add(new_host)
db.session.commit()
convention = Convention.query.get(data['convention_id'])
if (convention):
convention.host_company_id = new_host.id
db.session.commit()
return make_response(new_host.to_dict(), 201)
except ValueError:
return make_response({'errors': ['validation errors']}, 400)
api.add_resource(Hosts, '/hosts')
GET: Retrieves host companies, filtered by convention ID if provided
-Process:
>Check if a convention_id is provided
>Query the database for host companies associated with the convention
>Return the list of host companies as JSON
POST: Creates a new host company and associates it with a convention
-Process:
>Retrieve JSON data from the request
>Create a new HostCompany object using the data
>Add the new object to the database and commit the transaction
>Link the new host to a convention
>Return the new host company as JSON
HostCompaniesById Resource:
-> This manages the individual host companies
def get(self, id):
host = db.session.get(HostCompany, id)
if host:
return host.to_dict(), 200
return {'error': 'HostCompany not found'}, 404
def patch(self, id):
host = HostCompany.query.filter_by(id=id).first()
if host:
try:
data = request.get_json()
for attr in data:
setattr(host, attr, data[attr])
db.session.add(host)
db.session.commit()
return make_response(host.to_dict(), 202)
except ValueError:
return make_response({'errors': ['validation errors']}, 400)
else:
return make_response(jsonify({'error': 'HostCompany not found'}), 404)
def delete(self, id):
host = db.session.get(HostCompany, id)
if host:
db.session.delete(host)
db.session.commit()
return '', 204
return {'error': 'HostCompany not found'}, 404
api.add_resource(HostCompaniesById, '/hosts/<int:id>')
GET:Retrieves the specific host company by its ID
-Process:
>Query the database to get host company by ID
>Return the host company as JSON
PATCH:Updates a specific host company
-Process:
>Retrieve JSON data from the request
>Update the host company with the new data
>Commit the changes to the database
>Return the updated host company as JSON
DELETE:Deletes a specific host company by its ID
-Process:
>Query the database to get host company by ID.
>Delete the host company from the database
>Commit the changes.
Convention Resource:
-> This resource allows us to manage conventions, including the assosciation with specific convention areas and hosts
class Conventions(Resource):
def get(self):
convention_area_id = request.args.get('convention_area_id', type=int)
if convention_area_id:
conventions = Convention.query.filter_by(convention_area_id=convention_area_id).all()
else:
conventions = Convention.query.all()
return [convention.to_dict(only=('id', 'convention_name', 'days', 'convention_area_id', 'host_company_id')) for convention in conventions]
def post(self):
data = request.get_json()
try:
new_convention = Convention(
convention_name=data['convention_name'],
days=data['days'],
convention_area_id=data['convention_area_id'],
host_company_id=data.get('host_company_id')
)
db.session.add(new_convention)
db.session.commit()
return make_response(new_convention.to_dict(), 201)
except ValueError as e:
print('Error', e)
return make_response({'errors': ['validation errors']}, 400)
api.add_resource(Conventions, '/conventions')
GET: Retrieves all conventions with the option to be filtered by convention areas.
-Process:
>Check if a convention_area_id is provided
>Query the database for conventions associated with the convention area
>Return the list of convention as JSON
POST: Creates a new convention and association it with a host company and convention area.
-Process:
>Retrieve JSON data from the request
>Create a new Convention object using the data
>Add the new object to the database and commit the transaction
>Return the new convention as JSON
ConventionById Resource:
-> To manage individual conventions
class ConventionsById(Resource):
def get(self, id):
convention = db.session.get(Convention, id)
if convention:
return convention.to_dict(), 200
return {'error': 'Convention not found'}, 404
def patch(self, id):
convention = Convention.query.filter_by(id=id).first()
if convention:
try:
data = request.get_json()
for attr in data:
setattr(convention, attr, data[attr])
db.session.add(convention)
db.session.commit()
return make_response(convention.to_dict(), 202)
except ValueError:
return make_response({'errors': ['validation errors']}, 400)
else:
return make_response(jsonify({'error': 'Convention not found'}), 404)
def delete(self, id):
convention = db.session.get(Convention, id)
if convention:
db.session.delete(convention)
db.session.commit()
return '', 204
return {'error': 'Convention not found'}, 404
api.add_resource(ConventionsById, '/conventions/<int:id>')
GET :Retrieves a specific convention by its ID
-Process:
>Query the database to get the convention by ID.
>Return the convention as JSON
PATCH: Updates the specific convention
-Process:
>Retrieve the JSON data from the request
>Update the convention with the new data
>Commit the changes to the database
>Return the updated convention as JSON
DELETE: Deletes a specific convention by its ID
-Process:
>Query the database to get the convention by ID.
>Delete the convention from the database
>Commit the changes
Conclusion:
Building a RESTful API can be great for managing resources within a system. To ensure your code is both flexible and maintainable, it's crucial to structure it thoughtfully. By defining clear models, implementing CRUD operations, and utilizing serialization, we can effectively handle complex relationships between elements in a many to many relation ship system. This structured approach simplifies the process and helps it to be scallable.
Top comments (2)