Learning Flask being an iOS Developer
As an iOS Developer I’m sure sometime in your existence as such you’ve wondered: “What if I also code the backend of the app?”. This seems to be a great idea because you will not only be able to code the app’s backend but that backend will also be used to serve the other front-end technologies (Android, Web, etc.).
It makes much more sense to learn something new from something you already know. That’s the purpose of this course, teaching you how to develop a REST API using Flask always from an iOS dev perspective.
There are some requirements to understand and be able to follow along:
- iOS Development knowledge
- Python basic knowledge
I’m a Swift guy when it comes to iOS Development. I’ve had my experience with Objective-C in the past but now I’m just coding Swift and this series will be aimed towards it.
Regarding Python, a basic knowledge is required, you should be able to understand variables, indentation, functions, classes, etc. I won’t go into detail here because it would make the course too long. As the time of writing, Codecademy has a great free Python course which will be enough to understand this course.
Throughout the course we’ll be downloading and using different apps and software but everything will be free so I won’t list them here.
What will this course be about?
The main goal here is to teach you how to create a REST API with Flask but we should give it a “theme” for it to be easier to learn. We’ll be creating an API for a network of schools which will contain the schools and the students. We’ll also implement a login system and make some of the endpoints require authentication
A diagram of the database will, in theory, look like this:
If you don’t understand how a relational database work, don’t worry because I will explain everything you should know to complete this course. It’s a good idea anyway to take a course on databases in the future as it’s a key part when developing APIs.
Time to begin
First of all we need a Text Editor, it can be any text editor you like (in case you don’t have any, I recommend Atom or Sublime).
I tend to separate projects in folders and I suggest you do it the same way. I have a folder called “SchoolsManager” in my Desktop and everything we do will be inside. (I suggest not using spaces in any of the folders’ names because that can bring problems when using virtualenv)
Installing Python and virtualenv
If you already have Python and virtualenv installed, skip this section.
Go to https://www.python.org and download the latest stable version, as the time of writing that is 3.6.4
Keep in mind a python3 version is used in this course, Python2 is not supported.
To install ‘virtualenv’ open the Terminal and run this command:
sudo pip install virtualenv
What is ‘virtualenv’?
virtualenv creates an isolated Python environment. When you use libraries in Python (we’ll be using some in a few minutes) you are using a specific version of that library, usually the current latest one. Imagine you finish the API, it works great and you want to leave it running (to be able to use it) and start developing another API. When you create the new API you might want to use a newer version of the same library you are using for the first API but you don’t want to break the first one. That’s why we use ‘virtualenv’.
It’s like giving you the chance to keep coding old Swift versions in the newer XCode, wouldn’t you use that?
Now that we have the basics installed we have to create the ‘virtualenv’ for our project, to do so, go to your folder in the Terminal and run this command:
python3 -m venv venv
That creates a folder called ‘venv’ with all the necessary stuff to create a ‘virtualenv’ in your project folder. Easy, huh?
Now to enter that ‘virtualenv’ all you have to do is run:
. venv/bin/activate
And you’re in!
See how your command prompt changed and it says (venv):
If you want to leave the ‘virtualenv’ just run:
deactivate
How this course is focused
We could go ahead and straight into the code, right? Sure, but you’ll be overwhelmed by the amount of code in the first file and it has only 30 lines of code, instead, you’ll learn about the theory of everything which is behind the code and then we’ll go line by line comparing it to what we saw in the theory. So, try to understand these next concepts and then when we get to the code you’ll be amazed by how much you already understand of it.
REST in peace
We said we are going to create a REST API but what does REST mean? For an API to be REST, it has to follow some rules. Most “REST APIs” nowadays are not fully REST really, they just comply with the most basics rules, and that is the key concept of REST. When creating a REST API we have to remember that we are going to use ‘models’ for database handling and ‘resources’ for the object our client is going to interact with. In other words that means, when we are reading/writing from the database, we use the ‘model’, when we are receiving/sending data from the clients (mobile apps, web, etc.) we use the ‘resource’. Both the ‘models’ and the ‘resources’ are classes.
Database Alchemy
We are going to use a library called ‘SQLAlchemy’ for the writing/reading from the database. SQLAlchemy works with SQLite, PostgreSQL, MySQL, etc. What it does is letting us write our models to the database and then retrieve them.
JWT
JWT is the technology we use for authentication, the JWT website has a pretty good definition of how it works:
Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
Basically what that means is that when a user logs in to our API, the API will create a temporary ‘access_token’, will pass it to the user and it will be saved by the client. After that, with every request the client makes to the API the ‘access_token’ should be sent and this way the API will know the client is already logged in. This tokens expire after a certain time (for security reasons) so the user cannot be logged in forever. Usually, iOS apps use some kind of automatic re-login so the user doesn’t have to be logging in all the time.
Let’s see some code
I suggest you download the whole project and we’ll go step by step, you can download it here. Uncompress it and you get a folder.
The folder you just downloaded is called “code” and it contains all the code needed, place it as a sibling of “venv” inside your project folder:
Open the “code” folder in your Text Editor.
Some considerations
The folder “__pycache__” is generated automatically by python and it should not be uploaded to the repo nor deleted.
Inside the folders “models” and “resources”, the “__init__” file tells python the folder is a package an not just a folder.
SQLAlchemy
There’s a file called “db.py”, open it. The only thing that file does is importing ‘SQLAlchemy’ and creating a variable called ‘db’.
This is so every file in the API can call the same instance of ‘SQLAlchemy()’.
Models
UserModel
Let’s see the models first, remember, they are the object we will write and retrieve from the database.
Inside the “models” folder open “user.py”.
First we import ‘db’ which will let us use the ‘db’ variable to access the database:
from db import db
Then we declare the class:
class UserModel(db.Model):
Remember that a ‘model’ is what interacts with the database? Well, that’s why the model class inherits from ‘db.Model’.
Inside the declaration of the class we have something similar to a database table:
__tablename__ = 'users'id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80))
password = db.Column(db.String(80))
“__tablename__” is the name of the table obviously.
Then we define the Columns our “users” table will have and we define their parameters.
Just writing this, SQLAlchemy knows how to create the table for the users, easy, huh?
After that we have the “__init__” method which is pretty straightforward.
def __init__(self, username, password):
self.username = username
self.password = password
Notice how the id is not passed, this is because it increments its value by 1 for each new entry in the database. You can pass your own values for the id but this way we are passing ‘None’ which makes it auto-increment.
def save_to_db(self):
db.session.add(self)
db.session.commit()
This is how you add something to the database in SQLAlchemy, it’s pretty simple.
Now we have 2 “@classmethod”:
@classmethod
def find_by_username(cls, username):
return cls.query.filter_by(username=username).first()@classmethod
def find_by_id(cls, _id):
return cls.query.filter_by(id=_id).first()
“@classmethod” is a decorator which runs the function underneath in a certain way. I won’t get into detail about it but it means that the method can be called without a particular instance of the class.
You can call it by saying “UserModel.find_by_username(name)” or by creating a UserModel instance called ‘usermod’ (for example) and make the call as “usermod.find_by_username(name)”. It’s the same output because they don’t receive ‘self’ as a parameter but rather ‘cls’ which is a interpretation of the class where it’s called.
SchoolModel and StudentModel
They are so similar and related that we are going to look at both of them at the same time.
First the basics, we import the ‘db’:
from db import db
Both models have the same ‘find_by_name’ because we want to be able to search for both a student and a school by its name:
@classmethod
def find_by_name(cls, name):
return cls.query.filter_by(name=name).first()
And we have the methods to save and delete from the database:
def save_to_db(self):
db.session.add(self)
db.session.commit()def delete_from_db(self):
db.session.delete(self)
db.session.commit()
Now let’s talk about the database structure.
We said that we needed something like this:
We already know how to make SQLAlchemy create a table (as we did for the users) but here we have a relationship between the two tables.
The code in ‘StudentModel’ is:
__tablename__ = 'students'id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))school_id = db.Column(db.Integer, db.ForeignKey('schools.id'))
school = db.relationship('SchoolModel')
The “__tablename__” is defined and so are the Columns ‘id’ and ‘name’.
The diagram here helps a lot to understand how the ‘school_id’ and ‘school’ variables work.
A ‘foreign key’ is used to indicate a relationship between 2 tables, this line means:
school_id = db.Column(db.Integer, db.ForeignKey('schools.id'))
“Create a column called ‘school_id’ of type Integer with a relationship with the column ‘id’ from the table ‘schools’”
The line underneath just declares what type the ‘other side’ of the relationship is, in this case, “SchoolModel”:
school = db.relationship('SchoolModel')
Now take a look at the “SchoolModel” declaration of it’s table:
__tablename__ = 'schools'id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))students = db.relationship('StudentModel', lazy='dynamic')
Easy, huh?
We just have to specify there is a relationship between the “StudentModel” and us (being the “SchoolModel”). “lazy=’dynamic’” means that the variable ‘students’ is a query builder and it will fetch the data each time the call is made but not before.
Then we have the “__init__” methods and the “json” methods:
- StudentModel:
def __init__(self, name, school_id):
self.name = name
self.school_id = school_iddef json(self):
return {'name': self.name, 'school': self.school.name}
- SchoolModel:
def __init__(self, name):
self.name = namedef json(self):
return {'name': self.name, 'students': list(map(lambda x: x.json(), self.students.all()))}
The “__init__” methods are self explanatory and the “json” methods will be used when the Resource wants to send data to the client because we have to send a JSON back not an object.
Notice that the ‘map’ can be made with a “List Comprehension”, if you want, go ahead and change it. It’s a nice way to practice your Python skills by the way.
Resources
User
Take a look at what we are importing:
from flask_restful import Resource, reqparse
from models.user import UserModel
‘reqparser’ is for the Parser we are going to see in a minute, Resource will be the parent class of the ‘Resource’ and we are importing the UserModel to use it to create and retrieve data from the database.
The info to create the user, that is, username and password won’t get to us through the url, that’s extremely unsafe, it will be sent to us in the body of the request.
To get that data we need a parser:
parser = reqparse.RequestParser()
parser.add_argument('username',
type=str,
required=True,
help="This field cannot be left blank."
)
parser.add_argument('password',
type=str,
required=True,
help="This field cannot be left blank."
)
We declare which arguments from the data we receive is important to us, and we set a message in case the argument is missing or wrong. If there are more arguments in the body, the parser will not take them in count. Now let’s use it.
Ideally the only methods a Resource contains are the HTTP Verbs, that is: GET, POST, DELETE, PUT, etc.
The logic behind it will go to the Model.
The user just needs to be created (cannot be deleted or updated) in our API so the only function we make is: “post”:
def post(self):
data = UserRegister.parser.parse_args() if UserModel.find_by_username(data['username']):
return {"message": "User already exists"}, 400 user = UserModel(**data)
user.save_to_db() return {"message": "User created successfully."}, 201
We get the ‘data’ from the parser, we now it has a username and a password because those were our 2 arguments.
We check using the “UserModel” if a user with that username already exists in the database, if it already exists we return a message informing that with a status code 400 which means “Bad Request”.
If the users doesn’t exist already, we create a “UserModel” passing as argument all the data inside ‘data’. Because we are 100% sure there is a username and a password in ‘data’ and that there is nothing else we can use:
UserModel(**data)
which would be the same as:
UserModel(data['username'], data['password'])
Use the one you prefer the most.
Then we save the Model we just created to the database and return a message saying we created the user with status code 201 which means “Created”.
School
For the schools we need to be able to handle these things:
- GET a school by name
- POST a school with name
- DELETE a school with name
- GET all schools
We’ll divide this Resource in 2 Resources: “School” and “SchoolList” because we have 2 GET methods, one will be called with “/school” and the second one will be called with “/schools”. It will be clearer further on.
We are importing the same as in user.py but without ‘reqparse’ because we won’t get any info through the body of the requests. And of course, we are not importing ‘models.user’ but ‘models.school’
from flask_restful import Resource
from models.school import SchoolModel
Let’s start with the ‘School’ Resource.
It has a GET:
def get(self, name):
school = SchoolModel.find_by_name(name)
if school:
return school.json()
return {'message': 'School not found'}, 404
We ask the ‘SchoolModel’ to search for a specific name into the database and return ‘school.json()’ if there is something, otherwise we return a message with status code 404 which means “Not Found”.
It also has a POST:
def post(self, name):
if SchoolModel.find_by_name(name):
return {'message': "School '{}' already exists".format(name)}, 400school = SchoolModel(name)
try:
school.save_to_db()
except:
return {'message': 'An error occurred while creating the school'}, 500return school.json(), 201
First we need to see if there is already a school with that name in the database, if there is, we return a message with a status code 400. If not, we proceed by creating a new ‘SchoolModel’ with the name we were given. Then there’s a ‘try-except’ which is the same as try-catch in Swift. Here we are trying to save the model to the database, if for some reason that couldn’t be performed, it will return a message with status code 500 which means “Internal Server Error”. Finally, if we made it to the end we return ‘school.json()’ which is basically what we just inserted into the database with status code 201.
And finally it has a DELETE:
def delete(self, name):
school = SchoolModel.find_by_name(name)
if school:
school.delete_from_db()return {'message': 'School deleted'}
This is pretty straightforward, we just look for a ‘SchoolModel’ with that name in the database and if it exists we delete it. If it doesn’t exist we do nothing. The message is always the same because if the school is not found in the database then it’s the same result as if we would’ve deleted it.
Now let’s take a look at the SchoolList Resource:
It only has a GET method:
def get(self):
return {'schools': list(map(lambda x: x.json(), SchoolModel.query.all()))}
This is the same ‘map’ we saw earlier, if you want to change it for a List Comprehension that’s completely fine.
It might seem weird at first but ‘SchoolModel.query.all()’ makes sense, remember that “SchoolModel” inherits from ‘db.Model’ and that makes it capable of using ‘.query’ to make any kind of query into the table.
Student
This is the more complex of the 3 Resources we have.
Let’s take a look at what we import:
from flask_restful import Resource, reqparse
from flask_jwt import jwt_required
from models.student import StudentModel
We are importing Resource and ‘reqparse’, we are importing the ‘StudentModel’ from ‘models.student’ and we are importing ‘jwt_required’ from ‘flask_jwt’. For these requests we are going to use JWT Authentication, don’t worry, it’s easier than it sounds.
At the beginning of the class we set up the parser because we are only going to mind about the ‘school_id’ which will be passed to us through the requests’ bodies:
parser = reqparse.RequestParser()
parser.add_argument('school_id',
type=int,
required=True,
help="Every student needs a school id."
)
We have the GET method:
def get(self, name):
student = StudentModel.find_by_name(name)
if student:
return student.json()
return {'message': 'Student not found'}, 404
It works just as the school’s GET does.
We have the POST method as well:
@jwt_required()
def post(self, name):
if StudentModel.find_by_name(name):
return {'message': "A student with name '{}' already exists.".format(name)}, 400data = Student.parser.parse_args()student = StudentModel(name, **data)try:
student.save_to_db()
except:
return {'message': 'An error ocurred inserting the student.'}, 500return student.json(), 201
We have a decorator here!
Notice the “@jwt_required()” decorator, with that line before our method we are telling Flask that we need the JWT ‘access_token’ to run the method, if we don’t receive a valid token, the method will throw an error.
Inside the method we do the same as with the school using ‘StudentModel(name, **data)’ again but you can replace it with ‘StudentModel(name, data[‘name’], data[‘school_id’])’.
We also implemented a DELETE method:
@jwt_required()
def delete(self, name):
student = StudentModel.find_by_name(name)
if student:
student.delete_from_db()return {'message': 'Student deleted'}
It also has the “@jwt_required()” decorator so when sending the request, the client has to pass its ‘access_token’ in the Header of the request, we’ll see how later.
Finally we have ‘StudentList’ that works exactly the same way as ‘StoreList’:
def get(self):
return {'students': list(map(lambda x: x.json(), StudentModel.query.all()))}
Logging in
We’ve already seen how to register a new user using the POST method in the User Resource but we are missing something… Let the user log in.
We have a file called “security.py” which handles exactly that. ‘flask_jwt’ needs two methods to be defined, “authenticate” and “identity”. They are both in “security.py”.
Authenticate
The “authenticate” method return a UserModel if it was found in the database, otherwise it returns ‘None’:
def authenticate(username, password):
user = UserModel.find_by_username(username)
if user and user.password == password:
return user
The method takes 2 parameters: ‘username’ and ‘password’ and tries to find a UserModel with that username. If found, it then tries to match the user found in the database’s password with the password passed in the request. If it matches, it returns the user.
The user is not actually returned to the client because that would not be helpful, the client needs an ‘access_token’ for future requests, ‘flask_jwt’ handles this and sends the client the ‘access_token’.
Keep in mind the ‘access_token’ has information about the ‘id’ of the user logged in and not about the username and password
Identity
The second method is “identity”. It takes one parameter that is ‘payload’. This method is called when a method with the “@jwt_required()” decorator is called and it’s the responsible to return the appropriate user for the ‘access_token’. We’ll see an example later.
The method is simple:
def identity(payload):
user_id = payload['identity']
return UserModel.find_by_id(user_id)
Remember I said the ‘access_token’ has information about the ‘id’ of the logged in user, well, by accessing the ‘identity’ property from the payload we can then search in the database for a UserModel with that ‘id’ and return it.
This might’ve been a little confusing, here’s an example:
A user registers using the iOS app, the API returns a message “user created successfully”, the user now logs in with the same username and password, the app sends a log in request to the API passing username and password. ‘flask_jwt’ calls the “authenticate” method with the username and password sent by the app. It found it in the database so the API generates an ‘access_token’ and sends it back to the app. Now when the app wants to call a method that needs JWT Authentication (such as School POST for example) it needs to pass in the header of the request, the ‘access_token’. When that happens, ‘flask_jwt’ calls the “identity” method with the ‘access_token’, retrieves the ‘id’ and searches for a UserModel with that ‘id’ in the database.
Try to read that a couple times if it confusing, drawing the methods and the flow in a paper would certainly help, it’s really not too complicated.
app.py
When we use Python in general we like calling the main app “app.py”, and now it’s not time to do anything different.
Take a look at “app.py” and see which parts you can figure out by yourself.
We’ll start with the imports:
from flask import Flask
from flask_restful import Api
from flask_jwt import JWTfrom security import authenticate, identity
from resources.user import UserRegister
from resources.student import Student, StudentList
from resources.school import School, SchoolList
I’ll go into why we are importing each of them while going line by line in the rest of the code:
app = Flask(__name__)
We create a Flask() instance called as our program, for that we use the variable ‘__name__’ defined by Python. ‘Flask’ is imported for this.
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
We configure the database route (which is the folder from which we are running the server in the terminal) and set ‘SQLALCHEMY_TRACK_MODIFICATIONS’ to False. That’s because the Flask’s track modifications system is a bit better than SQLAlchemy’s.
Notice that we are using sqlite but PostrgeSQL or MySQL could be used as well.
app.secret_key = 'dante'
We define the ‘secret_key’ with which our ‘access_token’ will be encrypted.
Notice that it should be a much harder to guess key
api = Api(app)
We create the Api instance. ‘Api’ from ‘flask_restful’ is imported for this.
@app.before_first_request
def create_tables():
db.create_all()
Using the decorator “@app.before_first_request” we tell the database to create all tables, it does this going to all models and checking how each model want it’s table. ‘db’ needs to be imported but it’s going to be imported before we run this, you’ll see.
jwt = JWT(app, authenticate, identity)
We create a JWT instance passing as parameters the ‘app’ itself, the “authentication” method and the “identity” method. ‘JWT’ from ‘flask_jwt’ and ‘authenticate’ and ‘identity’ from ‘security’ were imported for this.
api.add_resource(School, '/school/<string:name>')
api.add_resource(Student, '/student/<string:name>')
api.add_resource(SchoolList, '/schools')
api.add_resource(StudentList, '/students')api.add_resource(UserRegister, '/register')
Then we define the Resources the Api will use, declaring the Resource class, the url and the url parameters we’ll receive.
if __name__ == '__main__':
from db import db
db.init_app(app)
app.run(port=5000, debug=True)
Finally we need to run the app and I know, the code is a little odd, but bear with me please.
“app.py” could be called from another file, so, to be sure that it will init the database and run the app just once (when we call it directly from the Terminal) we make that ‘if’ statement because when you call a file from the Terminal, Python will assign that file’s ‘__name__’ to “__main__”.
Then we import ‘db’ (This get’s imported before the first request so when create_tables() is called, ‘db’ is already imported), init the database and run the server in port 5000 (which is the default anyway) and in debug mode, which will help us track what the problem is if the app throws an error.
Running the server
It’s time to run the server and test it.
To run the server open the Terminal and go to your project’s folder.
Start ‘venv’, in case you forgot how, it’s:
. venv/bin/activate
Now enter the code folder:
cd code
And run the server:
python app.py
If that throws an error try with:
python3.6 app.py
You should see something like:
(venv) Dantes-MBP:code dantepuglisi$ python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 247-053-277
That means you are running the server in localhost (127.0.0.1 is localhost) on port 5000 as you defined previously.
To stop the server just press: Ctrl+C
Testing the API
We are going to use Postman to test the API, download it here.
Also download the collection for this project here and import it into Postman by going to “Collection”, “Import” and the Drag & Drop into the box.
You should see the Collection in the left side of the app. Inside the Collection there are 3 folders: “Students”, “Schools” and “Auth”. Each folder has the endpoints to interact with that specific Resource.
Authentication
Inside the “Auth” folder tap the “/register” endpoint, you should see something like this:
The route is “http://127.0.0.1:5000/register and the HTTP Verb is POST.
Press the “Headers” tab, in the headers we are passing ‘application/json’ for ‘Content-Type’, that means that the body is in JSON format.
Now tap the “Body” tab, we are passing ‘username’ and ‘password’ to register. You can change that data but be sure to change in the “/auth” endpoint as well.
Press “Send” and see the response, you’ll get something like this (remember to have the server running in the Terminal):
So we know the user is registered in the database, now go to the “/auth” endpoint.
We are passing the same header and the same info in the body to log in the same user we just created.
After pressing “Send” you get something like this:
Select the ‘access_token’ (without the quotes) and copy it (Cmd+C).
Before we continue with the other endpoints, do you see what happened?
When you made the first call to the API (/register), SQLAlchemy created a file called data.db (you can search for it, it’s inside the “code” folder) because of this:
@app.before_first_request
def create_tables():
db.create_all()
We called the “POST /register” endpoint in Postman which fired the “post” method from UserRegister, this one:
def post(self):
data = UserRegister.parser.parse_args() if UserModel.find_by_username(data['username']):
return {"message": "User already exists"}, 400 user = UserModel(**data)
user.save_to_db() return {"message": "User created successfully."}, 201
Then, we went ahead and tried to login with /auth passing the same username and password, which fired the “authenticate” method in “security.py”:
def authenticate(username, password):
user = UserModel.find_by_username(username)
if user and user.password == password:
return user
Notice that you can change what “/auth” returns aside from the ‘access_token’, take a look at the flask_jwt documentation for that.
School
Now we’ll move to the “Schools” folder. Once inside, click the “POST /school/<name>” endpoint.
You’ll get this:
Replace “<name> in the URL with the name for the School you choose, I’ll go with “asd” which is a pretty solid name for a school.
We are not passing info in the header or in the body because the “post” method from School(Resource) just takes a parameter “<name>” from the URL.
Press “Send” and see what returns. We just created a school.
You can play around and DELETE it, GET it and GET all schools.
We have a school, now we need the students, otherwise they’re not technically schools, right?
Student
Open the “Student” folder in the Collection and let’s test it as well.
We are going to create a new student, to do that, open the “POST /student/<name>” endpoint.
You’ll get:
Replace “<name>” with any name you like (‘dante’ seems to be a cool name).
Go ahead and press “Send”.
What happened? An error? Is it because that name is too awesome?
Worry not, you need to send the ‘access_token’ you got before, remember?
Press the “Headers” tab, the value for “Authorization” is empty. To solve this, in the value write (I’m using Swift syntax to make it easier to understand, imagine this is a Swift string): “JWT \(access_token)”
Notice that if you don’t have your ‘access_token’ to paste it there, you can go to the “/auth” endpoint and login again.
That would be:
Notice that there’s a space between “JWT” and the ‘access_token’.
We can now send the request again and voilà:
It matched us with the school named “asd” because it has a ‘school_id’ of 1 (it’s the first one we created) and we are passing ‘1’ for ‘school_id’ in the body, press the “Body” tab to check it.
Challenge time: If you create a student with a ‘school_id’ that doesn’t exist, it will create the student but throw an error, try to fix it so that the student doesn’t get created at all and that the endpoint returns a message “Please choose a valid school id”
To delete the database and start over, just go to your “code” folder inside your project and delete the file named “data.db”. After that, run the server again.
Creating the iOS app
Everything looks nice so far but we haven’t actually used the API in an app.
Let’s do that now.
I’ll guide you through the steps but won’t give you the finished project because I’m assuming you are an iOS Developer and will be able to do it on your own.
First create a single view project, name it “Schools Manager”, save it in the ‘Schools Manager’ folder install CocoaPods and Alamofire with it.
Create a new file called APIClient and add the following code, this is where the API methods will be:
Notice that instead of passing “Content-Type” as “application/json” in the headers, we are encoding the parameters as JSON with ‘JSONEncoding’. It works either way.
We save the ‘access_token’ in UserDefaults so in case we close the app and open it again, if the token hasn’t expired, we don’t need to login again.
In Main.Storyboard do something like this:
It’s just for testing purposes.
Then go to ViewController.swift, implement these two actions and connect them to the buttons. Don’t forget to also connect the textfields’ outlets:
You should have something like this:
Delete data.db if you haven’t already so we can test better.
Now before we run the app, make sure your server is running in the Terminal and press that “Play” button we all love so much.
Notice that you should run this in the simulator because your phone won’t have access to ‘localhost’ (127.0.0.1).
Enter a username and a password, press the “Register” button and take a look at the console, you just get this:
There you go, the user is created.
Now press the “Login” button and get this:
Well, we printed the ‘access_token’ twice but that’s not an issue, the thing is, we have the ‘access_token’!
Now we have to create the second screen and let the user pass once they login correctly.
The second screen is going to show just a UITableView with the schools as the sections’ headers and the students as rows of those sections.
It should be quite easy to do so let’s go ahead.
Something like this should do the trick (don’t forget to set the UIViewController as the ‘delegate’ and ‘datasource’ of the UITableView):
Create a new file called “SchoolsListViewController” and set it as the class for the new UIViewController in the ‘storyboard’ and for the ‘Storyboard ID’.
We have to add a completion handler to the API login function to know when we have the ‘access_token’ available, let’s do that. Change the “login” method to:
And the “loginButtonPressed” action to:
This code is pretty self-explanatory so let’s move on.
We still have to GET the list of schools and their students, there are a couple ways to do this. We can make 2 requests, a GET /schools and a GET /students or we can make just one call to GET /students and work our way to separate things. We’ll go with the second one so the first thing to do it go into APIClient.swift and paste this method:
We fetch the students (remember they come with their respective school name) and pass it in the completion handler in a tuple array.
Here’s the “SchoolsListViewController.swift” code, I tried to make it easy to read and understand and not so performant, I hope you don’t get sick when looking at nested for-in’s for example 😄:
It might seem tough at the beginning but it’s not that complicated, the magic happens in ‘addSchools’ and ‘createDiagramArray’ mainly so, try to understand that first and then the rest.
Before running make sure to have your server running in the Terminal and with some students and schools created in Postman because we don’t have the creation of a student implemented in our app.
Run the app and you should see something like:
Being “test” a school and “testStudent”, “testStudent4”, “testStudent3”, etc. students at “test” school.
“testStudent15”, “testStudent14", etc. are students at “testSchool3”, and so on.
Challenge time: Add two buttons (“Add school” and “Add student”) to be able to create Schools and Students from the app. Take in count you’ll need the ‘access_token’ for some of the requests, you can create the headers dictionary like this:
let headers: HTTPHeaders = [
"Authorization": "JWT jasgdbjkasbjasjdhasd",
]
And then pass it in the request:
"https://127.0.0.1:5000/student/\(name)", headers: headers
Hope you had a great time getting that API info into an iOS app and I’m happy to know you’ll be using this a lot in the future.
Final thoughts and next steps
Great, you now have a functional and tested REST API alongside with an iOS app which consumes it. Any service can interact with this API from your computer (or the one who’s running the server).
As next steps I recommend doing the following:
- Deploying the server to the cloud (Heroku, etc.) or to your own server
- Using Git for the server’s source control (and of course for the app as well)