diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..59ee0a502268715e007c5347f06a9e1690b0c4c1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+**/__pycache__/**
diff --git a/Procfile b/Procfile
index 244c130127e4d890b3bc6646b9ff8f41554031c5..bff65d4f21c3858677b3a44592d74f19c6ac014a 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-web: gunicorn app:app --log-file=-
+web: gunicorn app:app --log-file=-
\ No newline at end of file
diff --git a/README.md b/README.md
index a7a88e42c0f01ff5236c0f7663c700c4d531901d..4647bea5c21e8926205ee1d8d95d23bb04d625e1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,47 @@
-## Imperial PaaS Template: Python Flask
+# Python3 Flask Template
 
-Get started with this Python Flask template.
+A started template for Flask web apps using **Python 3.8**. This started template includes:
+
+- Dynamic frontend templates (not React)
+- Database support
+- Imperial LDAP user authentication
+- Multiple environments (development and production)
+
+
+This template is useful if you would like to create a backend REST API, optionally coupled with a simple dynamic frontend.
+
+## Getting started
+
+Once you have created your new app, take a few minutes to look through the files to familiarise yourself with the project structure.
+
+- `app.py` : entry point to the Flask app
+- `templates/` : contains the frontend dynamic HTML files
+- `static/` : contains the static frontend assets (images, stylesheets and scripts)
+- `blueprints/` : contains all the application routes
+- `models/` : contains all the database models
+- `database/` : contains the database creation
+- `config/` : contains the app settings for the different environments
+
+**The first change you should make** is to set the `APP_NAME` variable in `config/config.py` to whatever you app's name is.
+
+To start the application locally, you can just run `python3 app.py` and this will launch the app on port 5000 (by default).
+You will notice a message in the console saying: 
+
+`WARNING: Could not connect to the given database URL!`
+
+To fix this, you should set the environment variable DATABASE_URL accordingly. If you have PostgreSQL running locally, you can use that. Alternatively, you could use SQLite which is much simpler and does not require installation.
+
+If you do not want to use a database yet, you can ignore this warning and delete any routes that interact with the database.
+
+If you navigate to `http://localhost:5000`, you will see the response created by the route defined in `blueprints/home.py`.
+
+You will also notice the lines `Environment: production` and `Debug mode: off` when the Flask application starts in the console. To enable debug mode, you must set the environment variable `ENV` to `dev`, ie: `export ENV=dev` (see `config/config.py` for more details on different environments).
+
+## Tutorial 1: Adding a new route
+
+
+
+## Tutorial 2: Adding database interaction
+
+
+## Tutorial 3: Configuring a test environment
\ No newline at end of file
diff --git a/app.py b/app.py
index 566010be09c4dff4e16c1f566d90d6065601b4f5..709de365de095394de717712a1059590ab50d7de 100644
--- a/app.py
+++ b/app.py
@@ -1,15 +1,69 @@
-from flask import Flask, render_template
+from flask import Flask, send_from_directory, url_for
 
+from database.db import db
+from config.config import APP_NAME, ENV, get_app_config, get_static_url
 
-app = Flask(__name__)
+# Create and configure our Flask app
+app = Flask(__name__, static_url_path=get_static_url())
+app.url_map.strict_slashes = False
+app.config.from_object(get_app_config())
 
+db.init_app(app)
 
-@app.route('/')
-def index():
-    return render_template('index.html')
+# uri = os.getenv("DATABASE_URL")  # or other relevant config var
+# print(uri)
 
+# # if uri and uri.startswith("postgres://"):
+# #     uri = uri.replace("postgres://", "postgresql://", 1)
 
+# #     app.config["SQLALCHEMY_DATABASE_URI"] = uri
+# #     db = SQLAlchemy(app)
 
+# #     db.create_all()
 
+# #     if db.session.query(User).filter_by(username="testuser").first() is not None:
+# #         db.session.add(User(username='testuser', email='admin@example.com'))
+# #         db.session.commit()
 
-if __name__ == '__main__': app.run(debug=True)
\ No newline at end of file
+# #     if db.session.query(Entity).filter_by(username="my entity").first() is not None:
+# #         db.session.add(Entity(username='my entity', email='entity@example.com'))
+# #         db.session.commit()
+
+# # else:
+# #     print("No database created/linked with this application")
+
+
+# Serve all static assets for the frontend
+@app.route('/static/<path:path>')
+def serve_static_files(path):
+    return send_from_directory('static', path)
+
+
+# Register all routes from the blueprints module
+from blueprints.home import home_blueprint
+from blueprints.auth import auth_blueprint
+app.register_blueprint(home_blueprint)
+app.register_blueprint(auth_blueprint)
+
+# @app.route('/test-db')
+# def test_db():
+#     try:
+#         rows = ""
+
+#         for user in db.session.query(User).all():
+#             rows += str(user) + " "
+
+#         for e in db.session.query(Entity).all():
+#             rows += str(e) + " "
+
+#         print("All rows:", rows)
+#         return rows
+#     except:
+#         return "App database error"
+
+# Hook any custom Jinja templating functions
+from config import CUSTOM_TEMPLATE_FUNCTIONS
+app.jinja_env.globals.update(CUSTOM_TEMPLATE_FUNCTIONS)
+
+if __name__ == '__main__':
+    app.run()
\ No newline at end of file
diff --git a/auth/__init__.py b/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/auth/constants.py b/auth/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd473637a832a2fbc2341e5216534f82fc43e054
--- /dev/null
+++ b/auth/constants.py
@@ -0,0 +1,13 @@
+# LDAP server config
+LDAP_URL = "ldaps://ldaps-vip.cc.ic.ac.uk:636"
+LDAP_DN = "OU=Users,OU=Imperial College (London),DC=ic,DC=ac,DC=uk"
+
+# Relevant IC LDAP attributes
+TITLE = "extensionAttribute6"
+NAME = "givenName"
+SURNAME = "sn"
+DN = "distinguishedName"
+MEMBERSHIPS = "memberOf"
+
+# List of attributes to be parsed into dictionaries
+ATTRIBUTES_TO_SERIALISE = [DN, MEMBERSHIPS]
\ No newline at end of file
diff --git a/auth/ldap_auth.py b/auth/ldap_auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7a1cd71677c48e0f70f58e9c9a03846ba4e1406
--- /dev/null
+++ b/auth/ldap_auth.py
@@ -0,0 +1,43 @@
+from .constants import *
+from .ldap_handler import ldap_service
+
+WHITE_LIST = ["ictsec"]
+
+
+def ldap_login(username, password):
+    """
+    Perform (a) LDAP authentication and (b) additional (app specific) verifications
+    before granting access and returning the user LDAP attributes 'name, surname, title and memberships'.
+    """
+    ldap_attributes = ldap_service.ldap_login(
+        username, password, query_attrs=(TITLE, NAME, SURNAME, DN, MEMBERSHIPS)
+    )
+    return custom_authentication_checks(username, ldap_attributes)
+
+
+def custom_authentication_checks(username, ldap_attributes):
+    # ADD HERE CUSTOM HIGHER-LEVEL CHECKS
+    # e.g.:
+    #
+    # if 'doc' not in dict_attrs[DN]['OU']: # is 'doc' in the organisation sub-attribute?
+    #     if 'doc-all-students' not in dict_attrs[MEMBERSHIPS]['CN']: # is 'doc-all-students' among the memberships?
+    #         raise ldap.INVALID_CREDENTIALS # raise INVALID_CREDENTIALS exception
+    return ldap_attributes
+
+
+# To enforce a distinction between "student" and "staff", the `ldap_constant_TITLE` ldap attribute is
+# requested (see above) and associated to the user model. The following decorator is then an example
+# on how to leverage the title to implement title-based access (where DEFAULT_REDIRECTION is assigned
+# a convenient application route).
+# For inspiration on how to implement title-based access, refer to emarking's source code:
+#   https://gitlab.doc.ic.ac.uk/edtech/emarking
+#
+# def role_required(access_role, redirection_url=None):
+#     def decorator(f):
+#         @wraps(f)
+#         def decorated_function(*args, **kwargs):
+#             if current_user.title == access_role:
+#                 return f(*args, **kwargs)
+#             return redirect(url_for(redirection_url or DEFAULT_REDIRECTION))
+#         return decorated_function
+#     return decorator
diff --git a/auth/ldap_handler.py b/auth/ldap_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..855af320dc5ae778676a782a65e5a062d39931e1
--- /dev/null
+++ b/auth/ldap_handler.py
@@ -0,0 +1,85 @@
+import itertools
+import re
+from collections import defaultdict
+
+from .constants import *
+import ldap
+
+# Used to parse key-value LDAP attributes
+KEY_VAL_ATT_REGEX = "([A-Za-z0-9]+)=([A-Za-z0-9-@]+)"
+USERNAME_FILTER_TEMPLATE = "(&(objectClass=user)(sAMAccountName=%s))"
+BINDING_TEMPLATE = "%s@IC.AC.UK"
+
+
+class LdapConnectionHandler:
+    """
+    Adapter for the python-LDAP library.
+    The class simplifies the interaction with python-LDAP
+    to initialise an LDAPObject and handle the retrieval of
+    relevant LDAP user attributes.
+
+    EXAMPLE USAGE FOR LOGIN PURPOSES:
+        1. An LDAP object is initialised with LDAP server URL and base distinct name
+        2. A new connection is established with connect()
+        3. The LDAP binding for a given username and password is performed with ldap_login()
+        4. Relevant attributes are queried with query_attributes().
+    """
+
+    def __init__(self):
+        self.base_dn = LDAP_DN
+        self.server_url = LDAP_URL        
+
+    def ldap_login(self, username, password, query_attrs):
+        """
+        Performs basic LDAP authentication by binding on a fresh connection with `username` and `password`.
+        Throws INVALID_CREDENTIALS exception if authentication fails. On successful authentication,
+        retrieves the values stored on the LDAP server associated to `username` for the given `attributes`.
+        :param username: username credential
+        :param password: password credential
+        :param attributes: names of the attributes to filter for
+        :return: attr_name -> attr_value dict for given username
+        """
+        connection = ldap.initialize(self.server_url)
+        connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
+        connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+        connection.simple_bind_s(BINDING_TEMPLATE % username, password)
+        attributes = parse_ldap_attributes(
+            self.raw_attributes(username, query_attrs, connection)
+        )
+        connection.unbind_s()
+        return attributes
+
+    def raw_attributes(self, username, attributes, connection):
+        ldap_filter = USERNAME_FILTER_TEMPLATE % username
+        raw_res = connection.search(
+            self.base_dn, ldap.SCOPE_SUBTREE, ldap_filter, attributes
+        )
+        res_type, res_data = connection.result(raw_res)
+        _, filtered_attributes = res_data[0]
+        return filtered_attributes.items()
+
+
+###################################################################
+# U T I L I T I E S                                               #
+###################################################################
+def parse_ldap_attributes(attributes):
+    return {
+        k: ldap_attributes_to_dictionary(vs)
+        if k in ATTRIBUTES_TO_SERIALISE
+        else vs[0].decode("utf-8")
+        for k, vs in attributes
+    }
+
+
+def ldap_attributes_to_dictionary(attr_values):
+    items = (
+        re.findall(KEY_VAL_ATT_REGEX, item.decode("utf-8").replace(",", " "))
+        for item in attr_values
+    )
+    d = defaultdict(set)
+    for k, v in itertools.chain.from_iterable(items):
+        d[k].add(v)
+    return d
+
+
+ldap_service = LdapConnectionHandler()
diff --git a/blueprints/__init__.py b/blueprints/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/blueprints/auth.py b/blueprints/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..59362cd6d79a387c1f9d8ce8b89e63d3ecc5efd2
--- /dev/null
+++ b/blueprints/auth.py
@@ -0,0 +1,23 @@
+from flask import Blueprint, render_template, request
+from auth.ldap_auth import ldap_login
+import ldap
+
+auth_blueprint = Blueprint('auth', __name__, url_prefix='/auth')
+
+
+@auth_blueprint.route('/login', methods=['GET', 'POST'])
+def login():
+    if request.method == 'GET':
+        return render_template('login.html')
+    
+    # Handle post request
+    username = request.form.get('username')
+    password = request.form.get('password')
+    print(f'Got username={username} and password={password}')
+    if username and password:
+        try:
+            r = ldap_login(username, password)
+            return f"Logged in! LDAP response: {r}"
+        except ldap.INVALID_CREDENTIALS:
+            return "Invalid credentials."
+    return "please provide a username and password"
\ No newline at end of file
diff --git a/blueprints/home.py b/blueprints/home.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b9af5b843d2cdb21711f5222e79909502b7f71b
--- /dev/null
+++ b/blueprints/home.py
@@ -0,0 +1,35 @@
+from flask import Blueprint, render_template, request, redirect, url_for
+from database.db import db
+from models.user import Entity
+
+home_blueprint = Blueprint('home', __name__, url_prefix='/')
+
+@home_blueprint.route('')
+def home():
+    return render_template('index.html')
+
+@home_blueprint.route('/hello/<name>')
+def hello(name: str):
+    return "Hello, " + name
+
+
+# Example CRUD route
+
+@home_blueprint.route('/entities', methods=['GET', 'POST'])
+def entities():
+    if request.method == "GET":
+        try:
+            rows = ""
+            for e in db.session.query(Entity).all():
+                rows += str(e) + "<br>"
+
+            return rows
+        except:
+            return "App database error (have you setup a database for this app?)"
+    else:
+        username = request.form.get('username')
+        email = request.form.get('email')
+
+        db.session.add(Entity(username=username, email=email))
+        db.session.commit()
+        return redirect('/entities')
diff --git a/config/__init__.py b/config/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..65531fe0f925707a2dfda491db5347a357cb1270
--- /dev/null
+++ b/config/__init__.py
@@ -0,0 +1,12 @@
+# Define any custom Jinja2 functions here
+from flask import url_for
+from .config import URL_PREFIX
+
+# Get around the routing prefix issue inside the templates
+def url_for2(endpoint: str, **kwargs):
+    return URL_PREFIX + str(url_for(endpoint, **kwargs))
+
+
+CUSTOM_TEMPLATE_FUNCTIONS = {
+    "url": url_for2,
+}
diff --git a/config/config.py b/config/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0738ad1d71139fa28b33689437b769e7da2365a
--- /dev/null
+++ b/config/config.py
@@ -0,0 +1,50 @@
+import os
+
+# This should match exactly the name of the app you specified
+APP_NAME = "dbtestapp2"
+ENV = os.environ.get('ENV', 'prod').lower()
+URL_PREFIX = f"/{APP_NAME}" if ENV == 'prod' else ""
+
+# Get the static URL of the app (to get around the production path issue)
+def get_static_url():
+    if ENV == 'prod':
+        return f'/{APP_NAME}/static'
+    else:
+        return '/static'
+
+# Get the app configuration based on the ENV environment variable (default is prod)
+def get_app_config():
+    if ENV == 'prod':
+        return ProductionConfig()
+    else:
+        return DevelopmentConfig()
+
+
+# If you have created a database for this app, the connection string will be automatically
+# accessible through the DATABASE_URL environment variable.
+def get_db_url():
+    url = os.environ.get('DATABASE_URL')
+    if url is None:
+        print("WARNING: Could not connect to the given database URL!")
+
+    # For PostgreSQL databases, the conn string needs to start with "postgresql"
+    if url and url.startswith("postgres://"):
+        url = url.replace("postgres://", "postgresql://", 1)
+
+    return url
+
+
+# Flask App settings for production environment
+class ProductionConfig:
+    DEBUG = False
+    APPLICATION_ROOT = f"/{APP_NAME}"
+    SQLALCHEMY_DATABASE_URI = get_db_url()
+    SQLALCHEMY_TRACK_MODIFICATIONS = False
+
+
+# Flask App settings for local development enviroment
+class DevelopmentConfig:
+    DEBUG = True
+    SQLALCHEMY_DATABASE_URI = get_db_url()
+    SQLALCHEMY_TRACK_MODIFICATIONS = False
+
diff --git a/database/__init__.py b/database/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/database/db.py b/database/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e1eeb63ff7528d9ae956c7700724bf9989c1fe1
--- /dev/null
+++ b/database/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
\ No newline at end of file
diff --git a/models/__init__.py b/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/models/user.py b/models/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc408282b42e4f945b6cec4a97493b922ba71f5c
--- /dev/null
+++ b/models/user.py
@@ -0,0 +1,18 @@
+from database.db import db
+
+# class User(db.Model):
+#     id = db.Column(db.Integer, primary_key=True)
+#     username = db.Column(db.String(80), unique=True, nullable=False)
+#     email = db.Column(db.String(120), unique=True, nullable=False)
+
+#     def __repr__(self):
+#         return f'User({self.id}, {self.username}, {self.email})'
+
+
+class Entity(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    username = db.Column(db.String(80), unique=True, nullable=False)
+    email = db.Column(db.String(120), unique=True, nullable=False)
+
+    def __repr__(self):
+        return f'Entity({self.id}, {self.username}, {self.email})'
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 961decff13a3e8a347cea2ba1ded8d4ca92bd674..ee819a9eb4075093b1baf9ab1e83420597c61145 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,8 @@ gunicorn
 itsdangerous
 Jinja2
 peewee
-Werkzeug
\ No newline at end of file
+Werkzeug
+Flask-SQLAlchemy
+SQLAlchemy
+psycopg2-binary
+python_ldap
\ No newline at end of file
diff --git a/static/img/python-logo.png b/static/img/python-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a4bfafc006f4dfa6edee0d25b5efaa5a8edc6fc
Binary files /dev/null and b/static/img/python-logo.png differ
diff --git a/templates/index.html b/templates/index.html
index 8840789240f75e8f3ba3effa2f4bd23190e6c2e3..e83b98f04230f9ab4301c5d8fd983feacd7179bc 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,25 +1,38 @@
 <!DOCTYPE html>
 <html lang="en">
     <head>
-        <title>Heroku Flask Template</title>
+        <title>Flask Template</title>
         <meta charset="UTF-8">
         <meta name="viewport" content="width=device-width, initial-scale=1">
         
     </head>
     <body>
-        <h1 id="tittle">Heroku Flask Template</h1>
+        <h1 id="title">Imperial PaaS - Python Flask Template</h1>
         <nav>
             <ul>
-                <li><a href="index.html">HOME</a></li>
-                <li><a href="https://github.com/vetronus/heroku-flask-template">SOURCE CODE(GitHub)</a></li>
-                <li><a href="http://parthsarthee.com/">PARTH SARTHEE(Author)</a></li>
-                <li><a href="http://aroxbit.com">AROXBIT(Awesome Indie Startup)</a></li>
+                <li><a href="{{ url('home.home') }}">HOME</a></li>
+                <li><a href="{{ url('auth.login') }}">LOGIN</a></li>
             </ul>
         </nav>
-        <h2>What is Heroku Flask Template</h2>
+
+        <img src="{{ url('static', filename='img/python-logo.png') }}" width="100px" alt="python logo"/>
+
+        <h2>What is Flask Template</h2>
         <p>
-            Heroku Flask Template is a simple web app programmed in Python-3 using flask micro-framework. It is created for begginers to understand the basics of creating a flask web app and deploying it on the Heroku. It can also be used as a template to create your new flask web apps which can then easily be deployable on Heroku.
+            Flask Template is a simple web app programmed in Python-3 using flask micro-framework. It is created for begginers to understand the basics of creating a flask web app and deploying it on the Heroku. It can also be used as a template to create your new flask web apps.
         </p><br>
-        <h3>You can download this plugin, or modify its source code from <a href="https://github.com/vetronus/heroku-flask-template">GitHub</a></h3>
+
+        Example CRUD form:
+        <form method="post" action="{{ url('home.entities') }}">
+            Username:<br>
+            <input type="text" name="username">
+            <br>
+            Email:<br>
+            <input type="email" name="email">
+            <br>
+            <button type="submit">Create Entry</button>
+        </form>
+        <br>
+        <a href="{{ url('home.entities') }}">View entities</a>
     </body>
 </html>
\ No newline at end of file
diff --git a/templates/login.html b/templates/login.html
new file mode 100644
index 0000000000000000000000000000000000000000..6771198785f869b9c65a024f6f17212fd15f0cdd
--- /dev/null
+++ b/templates/login.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <title>Flask Template</title>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        
+    </head>
+    <body>
+        <h1 id="title">Imperial PaaS - Python Flask Template</h1>
+        <nav>
+            <ul>
+                <li><a href="{{ url('home.home') }}">HOME</a></li>
+                <li><a href="{{ url('auth.login') }}">LOGIN</a></li>
+            </ul>
+        </nav>
+
+        <form method="post">
+            LDAP Username:<br>
+            <input type="text" name="username">
+            <br>
+            Password:<br>
+            <input type="password" name="password">
+            <br>
+            <button type="submit">Login</button>
+        </form>
+    </body>
+</html>
\ No newline at end of file