CRUD stands for create, read, update, and delete. These functions are the four pillars of a complete CRUD API (and full-stack application, for that matter).
1. PREREQUISITES
Basic knowledge of python(flask)
Knowledge of HTML, and CSS.
Already have python installed/downloaded.
Text editor/ IDE
"If you don't have python installed and zero knowledge of flask, see my previous article on Building your First Python(Flask) Application "
2. DOWNLOAD AND INSTALL DEPENDENCIES AND TOOLS
This is a tool that replicates a server on your pc which can be used for hosting web applications locally on your system, i.e localhost.
The goal of XAMPP is to build an easy-to-install distribution for developers to get into the world of Apache. To make it convenient for developers, XAMPP is configured with all features turned on.
To install XAMPP, simply go to the apachefriends.org website or search XAMPP on your search engine and click the first link that pops up.
This should take us to the apache website.
Click on the download tab on the navbar, we should get to a page where we see the different versions for different OS (operating systems).
NB: I am using MacOS so I scrolled down.
Though PHP won't be used in this tutorial, it is advisable to download XAMPP with the latest PHP version.
So, download XAMPP based on your OS and the setup file should start downloading.
Run the setup file and follow the steps, you should get XAMPP installed.
After installing, run XAMPP.
On Windows,
On MacBook,
To access the apache and MySQL part on MacBook, click the manage servers section.
Now, start both Apache Web Server and MySQL.
If using MacBook, click on the welcome tab, then click on the application tab.
You should get to this page;
Then click on the PHPMyAdmin tab, you should get here,
If using windows, click on the admin button under actions on the same row as MySQL.
You should also get here;
OR both OS can simply type localhost on their browsers while apache and MySQL are running.
LIBRARIES
Run the following command in your command line one after the other, on macOS ensure to use pip3 instead of pip;
pymysql library - pip install PyMySQL
Flask - pip install flask
Flaskext - pip install Flask-MySQL
Gotten to this part? Cheers🍻
3. BUILD CRUD API
Create web pages and directories - index.html, login.html, register.html
Create our SQL database
Build Flask app and connect database
Build crud API
Test
CREATE WEB PAGES AND DIRECTORIES:
First, we create a base folder for all our files, let's call it crud
Next, we create two folders templates
and static
inside crud
and one file app.py
,
Next create three(3) HTML files index
, login
, register
inside templates
folder.
Let's open our project folder in our text editor(vs code)
/*********************************************/
CREATE OUR SQL DATABASE:
Launch your XAMPP and follow the above steps to get localhost on your browser;
Now let's create our database, first click on new;
Enter the database name in the input field as shown above, then click create; you should get a prompt to create a table as shown below.
Click on the SQL tab, Copy and paste the following SQL statements into the SQL query tab as shown below;
CREATE TABLE `crud`.`accounts` (`id` INT(11) AUTO_INCREMENT , `username` VARCHAR(200) NOT NULL , `email` VARCHAR(200) NOT NULL , `password` VARCHAR(200) NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;
Let's explain the above statements;
First statements;
CREATE TABLE 'crud'.'accounts'
means to create a table named accounts inside a database called crud.
(
`id` INT(11)
NOT
NULL AUTO_INCREMENT
means to create a column of name id that can only store integer values INT with length not greater than 11 i.e it can store 12345678901, 234567890, 1, 234, 5678 but can not store 123456789012 because it's length is greater than 11. Auto_increment means it updates its values itself.
username VARCHAR(200)
NOT
NULL
means to create a column of name username that can only store characters(letters, characters and numbers) with lengths not greater than 200. Not null means the column cannot be empty.
"This explanation applies for both the other two columns"
PRIMARY KEY (`id`))
means this column contains values that uniquely identify each row in a table.
)
NB: We won't be inserting values now.
Database created successfully 💃💃💃;
/*********************************************/
BUILD FLASK APP AND CONNECT DATABASE
Let's open the app.py file and copy and paste the following code;
from flask import Flask, render_template, session
from flaskext.mysql import MySQL
import pymysql
mysql =MySQL()
app = Flask(__name__, template_folder='templates')
app.config['MYSQL_DATABASE_USER'] = 'root'
app.config['MYSQL_DATABASE_DB'] = 'crud'
app.config['MYSQL_DATABASE_PASSWORD'] = ''
app.config['MYSQL_DATABASE_HOST'] = 'localhost'
mysql.init_app(app)
@app.route('/')
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
"For a detailed explanation of the above code, see my previous article on Building your First Python(Flask) Application "
Now, let's do a rush explanation of the above code,
Libraries imported:
Flask - We've imported the flask class here
render_template - to redirect to the template file defined in its argument after the successful execution of the function it was called in.
flaskext - to connect to MySQL database
pymysql - to perform SQL queries
session - a library that allows us to store data on the server
Initialization:
mysql = MySQL()
Here we create an instance of the MySQL() library
app=Flask(__name__,template_folder='templates')
This creates the flask app and tells it where our templates are
app.config['MYSQL_DATABASE_USER'] = 'root'
app.config['MYSQL_DATABASE_DB'] = 'crud'
app.config['MYSQL_DATABASE_PASSWORD'] = ' '
app.config['MYSQL_DATABASE_HOST'] = 'localhost'
Here we state the parameters to configure our flask app to MySQL, this can be edited to your parameters.
In my case my database name is crud,
I have no password, if you have please enter,
username is root,
the hostname is localhost which is Xampp (for windows users, the default port for MySQL is 3306 but if you use a different one, simply enter localhost: port number
.
So if you use port 3307 for example, your hostname would be localhost:3307
To know your port, it is visible on your xampp window👇;
mysql.init_app(app)
Finally, we initialize our mySQL instance with our configured app.
Before executing, edit your index.html
file like below and ensure your Xampp is running both MySQL and Apache
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Welcome
</body>
</html>
To run our app.py file, click on run, then run without debugging;
The terminal pops up and displays a link http://127.0.0.1:5000
under the warning message as shown below;
Copy and paste the link into your browser, it should look like this👇
As seen it displays the content of our index.html
file;
Now copy and paste the following code into the;
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Login</h1>
<a href="/">back to home</a>
<form action="" method="post">
<input type="text" name="username" id="" placeholder="Username">
<input type="password" name="password" id="" placeholder="Password">
<input type="submit" value="Login">
<p>Don't have an account? <a href="">Register</a></p>
</form>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Register</h1>
<a href="/">back to home</a>
<form action="" method="post">
<input type="text" name="username" id="" placeholder="Username">
<input type="email" name="" id="">
<input type="password" name="password" id="" placeholder="Password">
<input type="submit" value="Register">
<p>Already have an account? <a href="">Login</a></p>
</form>
</body>
</html>
edit your index.html
to this;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="/login">Login</a>
<br>
<a href="/register">Register</a>
<br>
Welcome
</body>
</html>
and edit your app.py
to this;
from flask import Flask, render_template, session
from flaskext.mysql import MySQL
import pymysql
mysql =MySQL()
app = Flask(__name__, template_folder='templates')
app.config['MYSQL_DATABASE_USER'] = 'root'
app.config['MYSQL_DATABASE_DB'] = 'crud'
app.config['MYSQL_DATABASE_PASSWORD'] = ''
app.config['MYSQL_DATABASE_HOST'] = 'localhost'
mysql.init_app(app)
@app.route('/')
def home():
return render_template('index.html')
@app.route('/login')
def login():
return render_template('login.html')
@app.route('/register')
def register():
return render_template('register.html')
if __name__ == '__main__':
app.run(debug=True)
We created two different functions in our app.py
file that will render to out login and register pages.
In our index.html
we linked our login and register pages using their @app.route('/login')
, @app.route('/register')
values respectively
Linking to the login page we used <a href="/login"></a>
Linking to the register page we used <a href="/register"></a>
Now, let's execute our app.py
file, we should have this below;
Upon clicking the login link
we navigate to this page👇
Now click back to home
link,
click on the register link
, we should navigate to this page👇
Take a break;
/*********************************************/
BUILD CRUD API
REGISTER
Delete your register function starting from its app.route and replace it with the following code;
@app.route('/register', methods=['GET','POST'])
def register():
text = ''
if request.method == 'POST' and 'username' in request.form and 'email' in request.form and 'password' in request.form:
username = request.form['username']
email = request.form['email']
password = request.form['password']
conn = mysql.connect()
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute(" SELECT * FROM accounts WHERE username = %s OR email = %s",(username,email,))
accounts = cur.fetchone()
if accounts:
text = "Account already exists"
else:
cur.execute("INSERT INTO accounts VALUES(NULL, %s, %s, %s)", (username, email, password,))
conn.commit()
text = "Account successfully created!"
elif request.method=='POST':
text = "Fill in the forms"
return render_template('register.html',text=text)
EXPLANATION;
text = ' '
This variable was created to display information on our web page based on different conditions.
if request.method == 'POST' and 'username' in request.form and 'email' in request.form and 'password' in request.form:
Earlier, we said request is used to access HTML form inputs, the above line of code does 4 things;
Checks for a form in our register.html file finds it, and checks if the method was set to POST, i.e when the user clicks submit button
Checks if an input field with the name username contains a value
Checks if an input field with the name password contains a value
Checks if an input field with the name email contains a value
HTTP METHODS: There are several HTTP methods, we have GET, POST, HEAD, PUT and DELETE. In this article, we will be dealing with only GET and POST.
GET: Used to request information from the server in this case our Flask(app) which has been initialized with our MySQL database.
POST: Used to send HTML form data to the server.
By default, all HTML forms have a method of GET, To handle both GET and POST requests, we add that in the decorator app.route()
method as shown in the code above.
So, once our user clicks on the input field submit, the above codes start to get executed.
username = request.form['username']
email = request.form['email']
password = request.form['password']
The above variables store data entered in the input fields.
conn = mysql.connect()
connects to our MySQL database and saves in a variable conn
cur = conn.cursor(pymysql.cursors.DictCursor)
creates a method that allows us to execute SQL queries and saves in a variable cur
cur.execute(" SELECT * FROM accounts WHERE username = %s OR email = %s",(username,email,))
Before inserting the information entered in our input fields, so we check if the username or email entered is already in our table, this is because no two users can have the same username and email address.
The SELECT * FROM selects from our table
WHERE specifies the condition
OR allows for query execution if at least one of the conditions is true
CONDITIONS: username == %s, email == %s
%s is simply string formatting, tells python that a value is missing, then states the value to replace the missing value in brackets as arguments, (username, email)
accounts = cur.fetchone()
The above query fetches the first row where any of the above conditions is true, and saves in a variable.
NB: fetchone()
is a function used to fetch a single row from a table.
if accounts:
Specifies a condition saying if a row that meets the above condition is found.
text = "Account already exists"
it displays the following info on our browser - "Account already exists"
This is to prevent duplicate entries.
Will explain later how our web page can access this text variable. Stay Tuned!
else:
Specifies what happens if if accounts:
condition doesn't evaluate to true.
cur.execute("INSERT INTO accounts VALUES(NULL, %s, %s, %s)", (username, email, password,))
The above query is executed if if accounts:
condition doesn't evaluate to true.
It works like SELECT
, the only difference is it inserts into our table the value entered by the user.
If you notice the column count doesn't match the value count.
VALUES(NULL, %s, %s, %s)
value count has four parameters
(username, email, password,)
column count has three parameters.
This is simply because, when creating our table, we specified the first column (id) to AUTO-INCREMENT which means it updates its values itself while we update the values of the other columns.
We have to specify NULL to avoid MySQL throwing an error - *"*column count doesn't match"
elif request.method=='POST':
If the user presses the submit button, but all the fields are empty
text = "Fill in the forms"
We display "Fill in the forms"
return render_template('register.html',text=text)
allows the user to redirect to the registration page upon clicking of register and link and creates a variable "msg" which stores information from different condition execution "text".
Before execution, edit your register page as follows;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Register</h1>
<a href="/">back to home</a>
<h3>{{ text }}</h3>
<form action="/register" method="post">
<input type="text" name="username" id="" placeholder="Username" required>
<input type="email" name="email" id="" placeholder="email" required>
<input type="password" name="password" id="" placeholder="Password" required>
<input type="submit" value="Register">
<p>Already have an account? <a href="/login">Login</a></p>
</form>
</body>
</html>
{{ text }}
is how we access python variables in HTML files as shown in the code above.
Before execution, ensure XAMPP is started and Apache and MySQL are running and open our database in our browser;
as seen above, before execution it is empty;
Now run the app.py file;
Navigate to the registration page;
Enter information;
we will be using, username - test, email - test@gmail.com and password - test
Click register;
As seen above "Account successfully created!"
If you get this error👇,
Your Apache and MySQL are probably not started on your XAMPP, simply start and re-execute code.
Now let's cross-check our database, to see if it inserted,
And as seen above our database has been updated.
Now that we can register, let's create a login system that verifies our details and logs us in.
LOGIN/LOGOUT
In login, we would be working with sessions.
sessions store data temporarily on the server, and to do that with Flask a secret is required to be set.
This secret is used by the session to access and store data.
The secret key can be any text but should be a string.
Copy and paste the following line of code after app = Flask(__name__, template_folder='templates')
in the app.py
file;
app.secret_key = 'secretkey'
Edit your login.html
file as follows;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Login</h1>
<a href="/">back to home</a>
<h3>{{ text }}</h3>
<form action="/login" method="post">
<input type="text" name="username" id="" placeholder="Username">
<input type="password" name="password" id="" placeholder="Password">
<input type="submit" value="Login">
<p>Don't have an account? <a href="/register">Register</a></p>
</form>
</body>
</html>
Edit the login function in the app.py
file as follows;
@app.route('/login', methods=['GET','POST'])
def login():
text = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
conn = mysql.connect()
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute('SELECT * FROM accounts WHERE username = %s AND password = %s', (username, password,))
user = cur.fetchone()
if user:
session['loggedin'] = True
session['id'] = user['id']
session['username'] = user['username']
return render_template('index.html', msg=text)
else:
text = 'Incorrect username/password!'
elif request.method == 'POST':
text = "Fill in the forms"
return render_template('login.html', msg=text)
The login function simply verifies if user input can be found in the database, if true, it creates a session.
The session is what is used to store information temporarily on a browser.
Information in sessions is stored in the form of a dictionary, see here for an explanation of a python dictionary.
To access the value of a dictionary in python, we use the key.
In the above case, we created the key to store user input as the values, this key can then be accessed later on in our HTML pages.
Now run your app.py
file;
Navigate to log in, and input the details of the user we created before;
Click login, we should get to the below page;
Now we need to change the home page for a logged-in user from that of a normal visitor, to do that,
Edit your index.html
as below;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{% if 'loggedin' not in session %}
<a href="/login">Login</a>
<br>
<a href="/register">Register</a>
<br>
Not logged in
{% endif %}
{% if 'loggedin' in session %}
<a href="/logout">Logout</a>
Welcome {{ session['username'] }}
{% endif %}
</body>
</html>
The above code simply checks for a key named "logged in" in our session dictionary, if found it displays a welcome message to the user, but if it doesn't it shows the register and login links.
Before testing, let's create our logout function;
Copy and paste the following into your app.py
file;
@app.route('/logout')
def logout():
session.pop('loggedin', None)
session.pop('id', None)
session.pop('username', None)
return render_template('index.html')
In python, to remove items from a dictionary we use the pop function.
As seen above we simply pop out all the session values from our dictionary to log users out.
Now execute your app.py
file;
As seen above the user is not logged in, now log in;
Now let's logout;
Congratulations;
PREVENTING BACK BUTTON
Yeah, everything works alright, but notice when the back button of your browser is clicked, it removes every initially created session, making our work seem meaningless.
To prevent this, javascript comes in.
Copy and paste the following code into our index.html
file and login.html
file;
<script language="javascript" type="text/javascript">
window.history.forward();
</script>
Currently, your index.html
file should look like this;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{% if 'loggedin' not in session %}
<script language="javascript" type="text/javascript">
window.history.forward();
</script>
<a href="/login">Login</a>
<br>
<a href="/register">Register</a>
<br>
Not logged in
{% endif %}
{% if 'loggedin' in session %}
<script language="javascript" type="text/javascript">
window.history.forward();
</script>
<a href="/logout">Logout</a>
Welcome {{ session['username'] }}
{% endif %}
</body>
</html>
and your login.html
;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script language="javascript" type="text/javascript">
window.history.forward();
</script>
<title>Document</title>
</head>
<body>
<h1>Login</h1>
<a href="/">back to home</a>
<h3>{{ text }}</h3>
<form action="/login" method="post">
<input type="text" name="username" id="" placeholder="Username">
<input type="password" name="password" id="" placeholder="Password">
<input type="submit" value="Login">
<p>Don't have an account? <a href="/register">Register</a></p>
</form>
</body>
</html>
Yeah, that's how we prevent the back button in the meantime. It may not be perfect but it works.
We come to the end of the blog!
Get the source code here
If you find this useful, leave a like, comment and follow for more.
Made with ❤️ by rivondave