Developing for the web with Flask; creating a web application.
Tags: webdev, python, beginners, database
Published: 2021-09-01 15:09:17

Hey there, In this tutorial i will be showing you how to develop your first fullstack web-app project with flask-python.
In this tutorial or rather a documentation of how i did it, buckle up dev, and may the ride begin!

##Prerequisites
- Basic python language skills.
- Basic command line commands.(linux)
- PIP( a package manager for python)
  [Installation](https://pip.pypa.io/en/stable/installation/)
allows you to install required libraries for a specific project, i.e: Flask
- IDE(vscode, atom, sublime)
- [Python](https://www.python.org/downloads/) installed.

##Contents
1. [Launch](#launch)
2. [Installation](#inst)
3. [Templating](#temp)
4. [Conditionals](#statements)
5. [Looping](#loops)
6. [Web Forms](#forms)
   * [Configurations](#configure)
   * [Templating forms](#form-templates)
* [Database](#db)
  * [Database migrations](#migrations)
  * [Database models](#models)
  * [Migrating Data to the DB](#migrate)
  * [Adding data to the database](#update-db)
* [Viewing Posts](#posts)
* [Summary](#summary)

##Launch <a name="launch"></a>
Now setup up your project structure.
launch your code editor and create a directory in it.
`mkdir blogger && cd blogger`
- Now create your starting files:
`mkdir app && cd app`
`touch app.py routes.py models.py`
- While in the app directory create two directories
`mkdir templates static`
- Navigate to the root directory `blogger` and create the following files
`touch blog.py config.py`
all is set and your project structure should be looking like this:-

```python
blogger/
|-app/
     |-templates #html files directory
     |-static #css& images directory
     |-__init__.py
     |-routes.py #site navigation capability
     |-models.py #db logic goes here
|-blog.py # main apprunning module
|-config.py

#tip: identation shows file location, eg; templates is in app dir
```
When working with external libraries it is good to run your project in a virtual environment, go ahead and install virtualenv via the terminal 
```py
pip install virtualenv
#create a virtual environment 
virtualenv bloggerenv
# you can name your env any name
# activating 
# windows
cd bloggerenv/Scripts/activate
# linux
source bloggerenv/bin/activate

#deactivating the env
deactivate 
```
###Installing libraries <a name="inst"></a>
We use the PIP to install the required libraries
```python
(bloggerenv) $pip install Flask 
# this are the underlying packages to start a project
```
open the __init__.py in app directory type this:
```python
#__init__.py
from flask import Flask
app = Flask(__name__) #flask object instance

from app import routes
```
* first we import the Flask class from the flask module.
* next we create the app object as an instance of the Flask class.
* then import the `routes` module, to be created later

Open the **routes.py** file and we create the first route and test if everything is working.
```python
from app import app

@app.route('/')
@app.route('/index')
def index():
  return "Hello, World!"
```
The first line imports the application instance.
This is a view function, what it does it returns the string Hello World, to the browser when the app is run.
The two first lines `@app.route` are called **decorators**, they create urls for the web-app.

Head over to the blog.py file and create the app running logic.
```python 
#blog.py
from app import app

if __name__ == '__main__:
    app.run()
```
With this our app is done and can be previewed in the browser.But before running Flask needs to be told how to import it, through the `FLASK_APP` environment.

```python
(bloggerenv) $ export FLASK_APP=blog.py
```
If you are using a windows OS use the keyword `set` instead of `export`.
You can run your app in the terminal now:

```py
(bloggerenv) $ flask run
 * Serving Flask app 'blog.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```
Copy the url `http://127.0.0.1:5000/` in the browser.
![App preview](https://blog.miguelgrinberg.com/static/images/mega-tutorial/ch01-hello-world.png)
Voila! app running and good.

##Templating <a name="temp"></a>
In this section i'll show how to work with templates to generate more elaborate web pages with more complex structures. 
We are going to use Jinja2, it is a templating engine for 
flask.
We are going to store the template/html files in the templates folder.
Create an `index.html` file and open it.
```py
app/
    |-templates/ 
               |-index.html
```
I want the homepage to display a custom welcome message to the user
In the routes file create a python dictionary that holds our mock user:
```py
from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username' : 'Developer'}
    return render_template('index.html', title='Home', user=user)
```
Here we import the `render_template` function from the `flask` module. It handles `html` rendering in python
Then we create mock data as a python dictionary for a user, this will change henceforth as we include a database.


Let's code the `index.html` file:-
```html
<!doctype html>
<html>
    <head>
        <title>{{ title }} - blogger</title>
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>
```
When we run this version of the app, we get a nice preview with the username printed out. 
![Jinja2](https://jinja.palletsprojects.com/en/3.0.x/templates/)substitutes `{{ ... }}` blocks with corresponding values, given by the arguments provided in the `render_template()` call.

![App in browser preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ys86v10pwdxq9d0jlgy.png)

###Conditional statements in jinja2 <a name="statements"></a>
Jinja2 supports control statements given inside `{%..%}`
Add a conditional statement to the `index.html` that will print the documents title when provided in the `render_template()` call and prints out a default title if None is provided.
Put this block in the `<head>` section:
```html
{% if title %}
  <title>{{ title }}</title>
{% else %}
  <title>App | blogger</title>
{% endif %}
```
###Loops <a name="loops"></a>
And yes jinja2 also supports looping, what did you expect!😂
lets add a mock data in our `routes.py` file for posts
```py
from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username' : 'Developer'}
    posts =[
        {
        'author': {'username': 'Guido'},
        'body': 'I designed python language'
        },
        {
         'author': {'username': 'Jack'},
        'body': 'Blue is a cool color'
        }
    ]
    return render_template('index.html', title='Homer', user=user, posts=posts)
```
- I create a list `posts` with nested dictionaries, where each element is a dictionary that has `author` & `body` fields.

Now head on to `index.html` and handle the rendering of posts in the browser, posts can be of any number and we need to tell the temlate how to render them all, for that case we use a `for` loop.

```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>{{ title }} - blogger</title> 
</head>
<body>
    {% if user.username %}
        <p>Hello, {{ user.username }}</p>
    {% else %}
        <p>Hello, World!</p>
    {% endif %}

    <h2>My posts!</h2>
    {% for post in posts %}
        <b>{{ post.author.username }}</b>
        <p>{{ post.body }}</p>
    {% endfor %}
</body>
</html>
```
Give this new version a try and voila!!🤯🤯
![app posts preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgxp6my041evvt259unk.png)

When the structure of the app gets bigger and bigger we need to separate some preferences, for example, when linking css files or having a navbar in our website, its needed anyway.😊  we ought to do it in a separate template file.
We call this template inheritance:-
- Go ahead and create; `base.html` in the templates directory.
```html
<!-- base.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">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    {% block title %} 
        <title>Document</title>
    {% endblock %}
</head>
<body>
    {% block content %}
       
    {% endblock %}
</body>
</html>
```
With this we can now simplify our `index.html` file:-
to inherit a template file we use the `{% extends %}` tag, at the top of the child template.
```html
{% extends 'base.html' %}
{% block title %}
    {% if user.username %}
        <title>{{ title }}- blogger</title>
    {% else %}
        <p>Blogger</p>
    {% endif %}
{% endblock %}
{% block content %}
    <h2>My posts!</h2>
    {% for post in posts %}
        <b>{{ post.author.username }}</b>
        <p>{{ post.body }}</p>
    {% endfor %}
{% endblock %}
```
Template inheritance allows us to have the same look in every page without duplication.
Hence enabling us to be 'DRY'.
If you go and run this the results will be the same as above.

As the application growsn it will require more functionality and features; like web forms, etc.

##Web forms. <a name="forms"></a>
In this chapter I'll show you how to create web forms to take user input via the browser.
We use an extension called **Flask-WTF**, it is a wrapper found in the package WTForms, that integrates well with Flask.
```py
(bloggerenv) $ pip install flask-wtf
```
####Configurations <a name="configure"></a>

For tutorial purposes I am going to create just a simple form.
But before that, I am going to set some configuration variables, for _separation of concerns_ i am going to define my app configs in the `config.py` module in the top most directory.
I'll use a class to store any config variables and new configuration variables can be added here.

```py
# config.py
import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
```
the `SECRET_KEY` configuration variable is an important config in most flask apps, it is used by the Flask-WTF to protect web forms against the [Cross Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery)(CSRF) attack.
The value of the secret key is set as an expression with two terms, joined by the or operator. The first term looks for the value of an environment variable, also called `SECRET_KEY`. The second term, is just a hardcoded string. 
For now the app is small and doesn't require much security, i'll just use the hardcoded string, but when deploying it needs to be replaced with a unique key, can be generated in the python shell using the `urandom` module
```py
>>>from os import urandom
>>>urandom(20)
b'\x1d\x00\x08\x8b \xd8\xae\xe9....
```
Now that i have the config set up, i have to tell Flask to read and apply it. This can be done right after the flask app instance in the `__init__.py` using the `app.config.from_object()` method:.
```py
from flask import Flask
from config import Config

app = Flask(__name__)
app.config.from_object(Config)

from app import routes
```
**Creating** the first form, once again having the separation of concerns in mind, in the `app directory` create a file `forms.py`
The Flask-WTF extension uses Python classes to represent web forms. A form class simply defines the fields of the form as class variables.

```py
#app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired

class PostForm(FlaskForm):
    username = StringField('User name', validators=[DataRequired()])
    post = TextAreaField('Post', validators=[DataRequired()])
    submit = SubmitField('Submit')
```
- Import the FlaskForm base class.
- For each form you will need you create a separate class variable, each field is given a label as the first argument.
- The `validators` field is used to attach validation behaviors to form fields.
-The `DataRequired` validator checks that a field is not submitted empty.

####Form Templates <a name="form-templates"></a>
Next is to add the form to a HTML template, so it gets rendered to the web page.
The `LoginForm` class knows how to render itself as HTML, this makes the next part fairly simple.
- create a file `new_post.html` in `app/template/new_post.html`
N/B:For uniformity inherit the `base.html` template inside the new template.
```html
{% extends 'base.html' %}
{% block title %}
    {% if title %}
        <title>{{ title }}- blogger</title>
    {% else %}
        <p>Blogger</p>
    {% endif %}
{% endblock %}
{% block content %}
    <form action="" method="POST" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }} <br>
            {{ form.username(size=32) }}
        </p>
        <p>
            {{ form.post.label }} <br>
            {{ form.post(size=120) }}
        </p>
        <p>
            {{ form.submit() }}
        </p>
    </form>
{% endblock %}
```
This template expects a form object be instantiated in the `PostForm` class to be given as an argument.Which is referenced as `form`, the argument will be sent by the `post` view function.
- the html `<form>` element is used as  the container for the web form.
- the `action` attribute tells the browser the url to use when the form data is submitted. when set to an empty string the form is submitted to the current URL in the browser.
- The method attribute specifies the HTTP request method to be used when submitting the form to the server.
- `novalidate` tells the browser not to validate the forms as that is the work of Flask application running in the server.
- The `form.hidden_tag()` template argument generates a hidden field that includes a token that is used to protect the form against CSRF attacks.
- The `{{ form.<field_name>.label }}` replaces the label element, and the `{{ form.<field_name>() }}` goes where the form field is needed.
[Read more](https://jinja.palletsprojects.com/en/3.0.x/templates/) on the jinja2 documentation.


####Form view function.
In order to render the Form to the web page a route functionality is needed, In the routes.py module add the following view function at the end.

```py
#....

# route to the post form
@app.route('/post')
def post():
    form = PostForm()
    return render_template('new_post.html', title='Add post', form=form)
```
- Here i imported the `PostForm` class from _forms.py_, instantiated an  object `form = PostForm()` from it and sent the object down to the template `form=form`.
- The `form=form` parses the form object to the template form, it is what's required to render the form fields.

When you run the app and navigate to `http://127.0.0.1:5000/post` a html form is displayed. But when you try to submit it a `method not allowed` is thrown. I'll fix that in a minute.

![app form fields jinja2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xl0u5oddt8ztgextbrjy.png)

Next I'm going to create the navigation links inside the `base.html` file.

```html
<!-- .... -->
<body>
    <div>
        <b>Blogger:</b>
        <a href="{{ url_for('index') }}">Home</a>
        <a href="{{ url_for('post') }}">Add Post</a>
    </div>
    <hr>
<!-- .... -->
```
Add the above block just below the `body` tag, when the app is run we get a simple navbar at the top. That actually works 😉.
- As you noticed when i created the navbar links i used a different method, the `url_for()` function from Flask. which generates URLs using iternal mapping of urls to view functions.[Read](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iii-web-forms) ahead and practice working with forms.

![App navbar preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5afyw1bs6wodmc6t9yc.png)

Remember when you tried running the app and an error came shouting, that was because the `post` view function didn't understand what to do.
To fix it we add the `methods=['POST', 'GET']` in the `route decorator`.
The new updated route should look like this
```py
# route to the post form
from flask import Flask, redirect, flash
@app.route('/post', methods=['POST', 'GET'])
def post():
    form = PostForm()
    if form.validate_on_submit():
        print(f"Hey, {form.username.data}! Your post '{form.post.data}'  was successfully submitted! ")
        return redirect('/index')
    return render_template('new_post.html', title='Add post', form=form)
```
- Here we use an `if` statement to validate the `form` data in the view function.
- The `validated_on_submit()` runs validation of a form, Call validate only if the form is submitted. This is a shortcut for form.is_submitted() and form.validate().
- Here I use the `print()` function to print a custom message in the terminal, later I will be using the Flask `flash()` function to flash messages in the web page.
- `redirect`: this function redirects to a specified url.
- `flash`: it flashes a message to the web page after an action has been carried. 

###Database. <a name="db"></a>

Flask doesn't support databases natively, it isn't opinionated in this field, so it gives you the freedom to choose across a variety of database options that best fits your application.

Databases can be separated into two, those that follow [_relational models_](https://en.wikipedia.org/wiki/Relational_model#:~:text=The%20relational%20model%20(RM)%20for,of%20tuples%2C%20grouped%20into%20relations.) and those that do not. referred as _NoSQL_ dbs.

We are going to use Flask-sqlalchemy, an extension that provides a Flask-friendly wrapper to the popular [**SQLAlchemy**](https://docs.sqlalchemy.org/en/14/) package, **SQLAlchemy** is an ORM[Object Relational Mapper](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping).
An ORM allows applications to manage databases using high level entities such as classes, objects & methods instead of tables and SQL. The work of an ORM is to convert high-level operations into database commands.
Installing _flask-sqlalchemy_ run this in the terminal.
`(blogger) $ pip install flask-sqlalchemy`

#### Database Migrations <a name="migrations"></a>

As the application continues to grow, it might need changes or updates, also the database needs be updated. This is done through migrations and the flask extension that handles this is : `Flask-migrate`.
This extension is a Flask wrapper for Alembic a flask database migration framework for SQLAlchemy. Install the extension;
`(blogger) $ pip install flask-migrate`

####Flask-SQLalchemy configuration

In the development phase I will be using SQLite database. It saves each database file on disk.
When the app reaches production, I will use a database server: PostgreSQL or MySQL.
Two configurations are required in the `config.py` module:

```py
import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
```
 The Flask extension gets the location of the apps database from the `SQLALCHEMY_DATABASE_URI` configuration variable.
- As I did with the `SECRET_KEY` variable fallback, I also provide a fallback value if the config variable doesn't define the _database url_.
- The `SQLALCHEMY_TRACK_MODIFICATIONS` configuration if set to `False` disables a `Flask-SQLAlchemy` feature that sends a signal to the application every time a change is about to be made to the database. 

The database is represented in the application by a _database instance_. This is done in the `app/__init__.py` file.
```py
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config.from_object(Config) #configuration 
db = SQLAlchemy(app) #database instance
migrate = Migrate(app, db)

from app import routes, models

```
- In this change of the __init__ file, I imported two new extensions: SQLAlchemy & flask_migrate.
- I have added a _db_ object, it represents the database.
- The migrate variable handles the migration engine.
- At the bottom I've imported a new module `models` , This module defines the structure of the database.

####Database models. <a name="models"></a>

Data stored in the database will be represented by a collection of classes, usually known as _database models_. The ORM layer will handle the translastions required for mapping objects created from this classes to the correct rows in the database tables. 
Let's create a database model, create a new file `app/models.py` 

```py
from app import db

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), index=True, unique=True)
    post = db.Column(db.String(120), index=True)

    def __repr__(self):
        return(f"<User>: {self.username}, <Post>: {self.post}")
```
- The `id` field is usually in all models, and is used as the _primary key_. Each user is assigned a new `id` and is stored in this field.
- The `username` & `post` field are defined as strings(in database is known as `VARCHAR`) and their maximum lengths specified.
- The Post class created above inherits from db.Model, a base class for all models from Flask-SQLAlchemy.
- Fields are created as instances of the `db.Column` class, whih takes field type as arguments.
- The `__repr__` tells python how to print objects of this class. It is useful for debugging.
Open python in a terminal try to assign some data to our class objects.
```py
(blogger) $ python
>>> from app import db
>>> from app.models import Post
>>> p = Post(username="John", post="Hello, World!")
>>> p
<User>: John, <Post>: Hello, World!
>>>
```

The model created above defines the initial database structure or _schema_ of this application.

####Create a migration repository. <a name="migrate"></a>
 
When the app continues to grow, we need to handle the changes such as adding new things, modifying or removing items.
- The Alembic extension takes care of this schema changes.
It maintains the _migration repository_, a directory in which it stores the migration scripts.
- Each a change is made to the database schema, a migration script is added to the repository with the details of the change.
- To apply the migrations to a database, these migration scripts are executed in the sequence they were created.

- The `flask db` sub-command is added by flask-migrate to manage everything related to database migrations.
- To create a migration repository :
```py
(blogger) $ flask db init
Creating directory E:\code\projects\blogger\migrations ...  done
Creating directory E:\code\projects\blogger\migrations\versions ...  done
Generating E:\code\projects\blogger\migrations\alembic.ini ...  done
Generating E:\code\projects\blogger\migrations\env.py ...  done
Generating E:\code\projects\blogger\migrations\README ...  done
Generating E:\code\projects\blogger\migrations\script.py.mako ...  done
Please edit configuration/connection/logging settings in 'E:\\code\\projects\\blogger\\migrations\\alembic.ini' before proceeding.
```
The `flask` command relies on the `FLASK_APP` environment variable, after a successful run a new `migrations` directory is added.
Now we need to create our fist database migration.

```py
(blogger) $ flask db migrate -m 'posts table'
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'post'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_post_post' on '['post']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_post_username' on '['username']'
Generating E:\code\projects\blogger\migrations\versions\549b927398fe_posts_table.py ...  done
```
After running this, no change has been made to the database it just generates a migration script. Now we need to make changes to the database, To do that we use a `flask` sub-command `flask db upgrade` in the python shell context.

```py 
$ flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 549b927398fe, posts table
```
At this point you can play with the database a little, Let's try to add the previous post to the database.
- The `db` changes are done in a database session context.
- To add data to the db we use `db.session.add(p)`, _p_ is the object created for the corresponding database table.
- To write the changes use: `db.session.commit()`.

```py
(blogger) $ python
>>> from app import db
>>> from app.models import Post
>>> p = Post(username="John", post="Hello, World!")
>>> p
<User>: John, <Post>: Hello, World!
>>> db.session.add(p)
>>> db.session.commit()
>>> Post.query.all()
[<User>: Ariana, <Post>: Testing 2, <User>: John, <Post>: Hello, World!]
>>>

```
- first import the database object from the app module
- Import the Post class from the `models` module.
- Create a Post object name it _p_.
- use the db session context to _add_ and _push_ the change to the db.
- The last command `Post.query.all()` answers queries and returns all posts in the database.

###Adding data to the database <a name="update-db"></a>

When a user types their data in the forms, and hits submit button the form data needs to be written in the database. 
To do that I shall add the db session commands in the view function `/post`.
Your new changed `app/routes.py` should look like this.
```py
# route to the post form
@app.route('/post', methods=['POST', 'GET'])
def post():
    form = PostForm()
    if form.validate_on_submit():
        add = Post(username=form.username.data, post=form.post.data)
        db.session.add(add)
        db.session.commit() 
        flash(f"Hey, {form.username.data}! Your post '{form.post.data}'  was successfully submitted! ")
        return redirect('/index') 
    return render_template('new_post.html', title='Add post', form=form)

```
Now when a user enters data in the web forms and hits submit the data gets written to the database, we can view the data in a browser.

#### Viewing Posts in the Web-page <a name="posts"></a>

This part is rather simple, I just need to create a template that renders the posts in the database I will just use a `for` loop to loop through the available posts in the database.

Head over to the IDE and create a new template file, name it whatever you prefer. `app/templates/all_posts.html`

```html
{% extends 'base.html' %}
{% block title %}
    {% if title %}
        <title>{{ title }}- blogger</title>
    {% else %}
    <p>Blogger</p>
        {% endif %}
{% endblock %}

{% block content %} 
    <h2>Published Posts.</h2>
    {% for post in posts %}
        <b>{{ post.username }}</b>
        <p>{{ post.post }}</p>
    {% endfor %}
{% endblock %}

```

When you run the application and navigate to `http://127.0.0.1:5000/view` all our posts are displayed in the web-page.

![app preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v65qj5rowr5htwf2q80g.png)

Now we can head and add a nav link for viewing all our posts, this is will be added in the `base.html` file:

```py
<a href="{{ url_for('view') }}">Posts</a>
```
Add this line of code in the div inside the `base` template file. Preview the app in the browser and a new a navbar link has been added.

###Summary <a name="summary"></a>
In this article I took you through working with flask to create a minimal web application that takes user input via a web form and saves it in a database and retrieves it and displays it in a web-page.
- I worked you on using the Flask-SQLAlchemy and integrating a flask app with a database.
- Creating a database and managing the database modifications.
- Templating and rendering web-pages.
Go ahead and play with the UI by changing the `style.css` inside the `app/static` directory

```css
body {
    background-color: #7fffd4;
}
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at27fnxwgvrlzufmpl37.png)

When I add a the background color property the web page color changed, I shall write more on this in another article.

Thank you for taking your time to go through this article, Any feedback, advice or help is appreciated. Drop me a comment for any issue with running the code or improvement suggestions ;-)

In the next article i'll cover on deploying this flask app to heroku.
**References:** [https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world]

The whole code is available on [Github](https://github.com/Willbeckh/flask-app-tutorial), go ahead and star the repo or clone it for your practice.

Thank you @grayhat and Lux Tech Academy.

Be cool and Keep coding! mic drop...dev out