Authentication In Vue.js

Authentication is a very necessary feature for applications that store user data. It’s a process of verifying the identity of users, ensuring that unauthorized users cannot access private data — data belonging to other users. This leads to having restricted routes that can only be accessed by authenticated users. These authenticated users are verified by using their login details (i.e. username/email and password) and assigning them with a token to be used in order to access an application’s protected resources.

In this article, you’re going to be learning about:

  1. Vuex Configuration with Axios
  2. Defining Routes
  3. Handling Users
  4. Handling Expired Token

Dependencies

We will be working with the following dependencies that help in authentication:

  • Axios
    For sending and retrieving data from our API
  • Vuex
    For storing data gotten from our API
  • Vue-Router
    For navigation and protection of Routes

We will be working with these tools and see how they can work together to provide robust authentication functionality for our app.

The Backend API

We will be building a simple blog site, which will make use of this API. You can check out the docs to see the endpoints and how requests should be sent.

From the docs, you’ll notice few endpoints are attached with a lock. This is a way to show that only authorized users can send requests to those endpoints. The unrestricted endpoints are the /register and /login endpoints. An error with the status code 401 should be returned when an unauthenticated user attempts to access a restricted endpoint.

After successfully logging in a user, the access token alongside some data will be received in the Vue app, which will be used in setting the cookie and attached in the request header to be used for future requests. The backend will check the request header each time a request is made to a restricted endpoint. Don’t be tempted to store the access token in the local storage.

Scaffold Project

Using the Vue CLI, run the command below to generate the application:

vue create auth-project

Navigate into your new folder:

cd auth-project

Add the vue-router and install more dependencies — vuex and axios:

vue add router
    npm install vuex axios

Now run your project and you should see what I have below on your browser:

npm run serve

1. Vuex Configuration with Axios

Axios is a JavaScript library that is used to send requests from the browser to APIs. According to the Vuex documentation;

“Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.”

What does that mean? Vuex is a store used in a Vue application that allows us to save data that will be available to every component and provide ways to change such data. We’ll use Axios in Vuex to send our requests and make changes to our state (data). Axios will be used in Vuex actions to send GET and POST, response gotten will be used in sending information to the mutations and which updates our store data.

To deal with Vuex resetting after refreshing we will be working with vuex-persistedstate, a library that saves our Vuex data between page reloads.

npm install --save vuex-persistedstate

Now let’s create a new folder store in src, for configuring the Vuex store. In the store folder, create a new folder; modules and a file index.js. It’s important to note that you only need to do this if the folder does not get created for you automatically.

import Vuex from 'vuex';
import Vue from 'vue';
import createPersistedState from "vuex-persistedstate";
import auth from './modules/auth';

// Load Vuex
Vue.use(Vuex);
// Create store
export default new Vuex.Store({
  modules: {
    auth
  },
  plugins: [createPersistedState()]
});

Here we are making use of Vuex and importing an auth module from the modules folder into our store.

Modules

Modules are different segments of our store that handles similar tasks together, including:

Before we proceed, let’s edit our main.js file.

import Vue from 'vue'
import App from './App.vue'
import router from './router';
import store from './store';
import axios from 'axios';

axios.defaults.withCredentials = true
axios.defaults.baseURL = 'https://gabbyblog.herokuapp.com/';

Vue.config.productionTip = false
new Vue({
  store,
  router,
  render: h => h(App)
}).$mount('#app')

We imported the store object from the ./store folder as well as the Axios package.

As mentioned earlier, the access token cookie and other necessary data got from the API need to be set in the request headers for future requests. Since we’ll be making use of Axios when making requests we need to configure Axios to make use of this. In the snippet above, we do that using axios.defaults.withCredentials = true, this is needed because by default cookies are not passed by Axios.

aaxios.defaults.withCredentials = true is an instruction to Axios to send all requests with credentials such as; authorization headers, TLS client certificates, or cookies (as in our case).

We set our axios.defaults.baseURL for our Axios request to our API This way, whenever we’re sending via Axios, it makes use of this base URL. With that, we can add just our endpoints like /register and /login to our actions without stating the full URL each time.

Now inside the modules folder in store create a file called auth.js

//store/modules/auth.js

import axios from 'axios';
const state = {

};
const getters = {

};
const actions = {

};
const mutations = {

};
export default {
  state,
  getters,
  actions,
  mutations
};
state

In our state dict, we are going to define our data, and their default values:

const state = {
  user: null,
  posts: null,
};

We are setting the default value of state, which is an object that contains user and posts with their initial values as null.

Actions

Actions are functions that are used to commit a mutation to change the state or can be used to dispatch i.e calls another action. It can be called in different components or views and then commits mutations of our state;

Register Action

Our Register action takes in form data, sends the data to our /register endpoint, and assigns the response to a variable response. Next, we will be dispatching our form username and password to our login action. This way, we log in the user after they sign up, so they are redirected to the /posts page.

async Register({dispatch}, form) {
  await axios.post('register', form)
  let UserForm = new FormData()
  UserForm.append('username', form.username)
  UserForm.append('password', form.password)
  await dispatch('LogIn', UserForm)
},

Login Action

Here is where the main authentication happens. When a user fills in their username and password, it is passed to a User which is a FormData object, the LogIn function takes the User object and makes a POST request to the /login endpoint to log in the user.

The Login function finally commits the username to the setUser mutation.

async LogIn({commit}, User) {
  await axios.post('login', User)
  await commit('setUser', User.get('username'))
},

Create Post Action

Our CreatePost action is a function, that takes in the post and sends it to our /post endpoint, and then dispatches the GetPosts action. This enables the user to see their posts after creation.

async CreatePost({dispatch}, post) {
  await axios.post('post', post)
  await dispatch('GetPosts')
},

Get Posts Action

Our GetPosts action sends a GET request to our /posts endpoint to fetch the posts in our API and commits setPosts mutation.

async GetPosts({ commit }){
  let response = await axios.get('posts')
  commit('setPosts', response.data)
},

Log Out Action

async LogOut({commit}){
  let user = null
  commit('logout', user)
}

Our LogOut action removes our user from the browser cache. It does this by committing a logout:

Mutations
const mutations = {
    setUser(state, username){
        state.user = username
    },
    setPosts(state, posts){
        state.posts = posts
    },
    LogOut(state){
        state.user = null
        state.posts = null
    },
};

Each mutation takes in the state and a value from the action committing it, aside Logout. The value gotten is used to change certain parts or all or like in LogOut set all variables back to null.

Getters

Getters are functionalities to get the state. It can be used in multiple components to get the current state. The isAuthenticatated function checks if the state.user is defined or null and returns true or false respectively. StatePosts and StateUser return state.posts and state.user respectively value.

const getters = {
    isAuthenticated: state => !!state.user,    
    StatePosts: state => state.posts,
    StateUser: state => state.user,
};

Now your whole auth.js file should resemble my code on GitHub.

Setting Up Components

1. NavBar.vue And App.vue Components

In your src/components folder, delete the HelloWorld.vue and a new file called NavBar.vue.

This is the component for our navigation bar, it links to different pages of our component been routed here. Each router link points to a route/page on our app.

The v-if="isLoggedIn" is a condition to display the Logout link if a user is logged in and hide the Register and Login routes. We have a logout method which can only be accessible to signed-in users, this will get called when the Logout link is clicked. It will dispatch the LogOut action and then direct the user to the login page.

<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/posts">Posts</router-link> |
    <span v-if="isLoggedIn">
      <a @click="logout">Logout</a>
    </span>
    <span v-else>
      <router-link to="/register">Register</router-link> |
      <router-link to="/login">Login</router-link>
    </span>
  </div>
</template>
<script>
export default {
  name: 'NavBar',
  computed : {
      isLoggedIn : function(){ return this.$store.getters.isAuthenticated}
    },
    methods: {
      async logout (){
        await this.$store.dispatch('LogOut')
        this.$router.push('/login')
      }
    },
}
</script>
<style>
#nav {
  padding: 30px;
}
#nav a {
  font-weight: bold;
  color: #2c3e50;
}
a:hover {
  cursor: pointer;
}
#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

Now edit your App.vue component to look like this:

<template>
  <div id="app">
    <NavBar />
    <router-view/>
  </div>
</template>
<script>
// @ is an alias to /src
import NavBar from '@/components/NavBar.vue'
export default {
  components: {
    NavBar
  }
}
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

Here we imported the NavBar component which we created above and placed in the template section before the <router-view />.

2. Views Components

Views components are different pages on the app that will be defined under a route and can be accessed from the navigation bar. To get started Go to the views folder, delete the About.vue component, and add the following components:

Home.vue

Rewrite the Home.vue to look like this:

<template>
  <div class="home">
  <p>Heyyyyyy welcome to our blog, check out our posts</p>
  </div>
</template>
<script>

export default {
  name: 'Home',
  components: {
  }
}
</script>

This will display a welcome text to the users when they visit the homepage.

Register.vue

This is the Page we want our users to be able to sign up on our application. When the users fill the form, their information is been sent to the API and added to the database then logged in.

Looking at the API, the /register endpoint requires a username, full_name and password of our user. Now let’s create a page and a form to get those information:

<template>
  <div class="register">
      <div>
          <form @submit.prevent="submit">
            <div>
              <label for="username">Username:</label>
              <input type="text" name="username" v-model="form.username">
            </div>
            <div>
              <label for="full_name">Full Name:</label>
              <input type="text" name="full_name" v-model="form.full_name">
            </div>
            <div>
              <label for="password">Password:</label>
              <input type="password" name="password" v-model="form.password">
            </div>
            <button type="submit"> Submit</button>
          </form>
      </div>
      <p v-if="showError" id="error">Username already exists</p>
  </div>
</template>

In the Register component, we’ll need to call the Register action which will receive the form data.

<script>
import { mapActions } from "vuex";
export default {
  name: "Register",
  components: {},
  data() {
    return {
      form: {
        username: "",
        full_name: "",
        password: "",
      },
      showError: false
    };
  },
  methods: {
    ...mapActions(["Register"]),
    async submit() {
      try {
        await this.Register(this.form);
        this.$router.push("/posts");
        this.showError = false
      } catch (error) {
        this.showError = true
      }
    },
  },
};
</script>

We start by importing mapActions from Vuex, what this does is to import actions from our store to the component. This allows us to call the action from the component.

data() contains the local state value that will be used in this component, we have a form object that contains username, full_name and password, with their initial values set to an empty string. We also have showError which is a boolean, to be used to either show an error or not.

In the methods we import the Register action using the Mapactions into the component, so the Register action can be called with this.Register .

We have a submit method this calls the Register action which we have access to using this.Register, sending it this.form. If no error is encountered we make use of this.$router to send the user to the login page. Else we set showError to true.

Having done that, we can include some styling.

<style scoped>
* {
  box-sizing: border-box;
}
label {
  padding: 12px 12px 12px 0;
  display: inline-block;
}
button[type=submit] {
  background-color: #4CAF50;
  color: white;
  padding: 12px 20px;
  cursor: pointer;
  border-radius:30px;
}
button[type=submit]:hover {
  background-color: #45a049;
}
input {
  margin: 5px;
  box-shadow:0 0 15px 4px rgba(0,0,0,0.06);
  padding:10px;
  border-radius:30px;
}
#error {
  color: red;
}
</style>
Login.vue

Our LogIn page is where registered users, will enter their username and password to get authenticated by the API and logged into our site.

<template>
  <div class="login">
    <div>
      <form @submit.prevent="submit">
        <div>
          <label for="username">Username:</label>
          <input type="text" name="username" v-model="form.username" />
        </div>
        <div>
          <label for="password">Password:</label>
          <input type="password" name="password" v-model="form.password" />
        </div>
        <button type="submit">Submit</button>
      </form>
      <p v-if="showError" id="error">Username or Password is incorrect</p>
    </div>
  </div>
</template>

Now we’ll have to pass our form data to the action that sends the request and then push them to the secure page Posts

<script>
import { mapActions } from "vuex";
export default {
  name: "Login",
  components: {},
  data() {
    return {
      form: {
        username: "",
        password: "",
      },
      showError: false
    };
  },
  methods: {
    ...mapActions(["LogIn"]),
    async submit() {
      const User = new FormData();
      User.append("username", this.form.username);
      User.append("password", this.form.password);
      try {
          await this.LogIn(User);
          this.$router.push("/posts");
          this.showError = false
      } catch (error) {
        this.showError = true
      }
    },
  },
};
</script>

We import Mapactions and use it in importing the LogIn action into the component, which will be used in our submit function.

After the Login action, the user is redirected to the /posts page. In case of an error, the error is caught and ShowError is set to true.

Now, some styling:

<style scoped>
* {
  box-sizing: border-box;
}
label {
  padding: 12px 12px 12px 0;
  display: inline-block;
}
button[type=submit] {
  background-color: #4CAF50;
  color: white;
  padding: 12px 20px;
  cursor: pointer;
  border-radius:30px;
}
button[type=submit]:hover {
  background-color: #45a049;
}
input {
  margin: 5px;
  box-shadow:0 0 15px 4px rgba(0,0,0,0.06);
  padding:10px;
  border-radius:30px;
}
#error {
  color: red;
}
</style>
Posts.vue

Our Posts page is the secured page that is only available to authenticated users. On this page, they get access to posts in the API’s database. This allows the users to have access to posts and also enables them to create posts to the API.

<template>
  <div class="posts">
      <div v-if="User">
        <p>Hi {{User}}</p>
      </div>
      <div>
          <form @submit.prevent="submit">
            <div>
              <label for="title">Title:</label>
              <input type="text" name="title" v-model="form.title">
            </div>
            <div>
              <textarea name="write_up" v-model="form.write_up" placeholder="Write up..."></textarea>
            </div>
            <button type="submit"> Submit</button>
          </form>
      </div>
      <div class="posts" v-if="Posts">
        <ul>
          <li v-for="post in Posts" :key="post.id">
            <div id="post-div">
              <p>{{post.title}}</p>
              <p>{{post.write_up}}</p>
              <p>Written By: {{post.author.username}}</p>
            </div>
          </li>
        </ul>
      </div>
      <div v-else>
        Oh no!!! We have no posts
      </div>
  </div>
</template>

In the above code, we have a form for the user to be able to create new posts. Submitting the form should cause the post to be sent to the API — we’ll add the method that does that shortly. We also have a section that displays posts obtained from the API (in case the user has any). If the user does not have any posts, we simply display a message that there are no posts.

The StateUser and StatePosts getters are mapped i.e imported using mapGetters into Posts.vue and then they can be called in the template.

<script>
import { mapGetters, mapActions } from "vuex";
export default {
  name: 'Posts',
  components: {

  },
  data() {
    return {
      form: {
        title: '',
        write_up: '',
      }
    };
  },
  created: function () {
    // a function to call getposts action
    this.GetPosts()
  },
  computed: {
    ...mapGetters({Posts: "StatePosts", User: "StateUser"}),
  },
  methods: {
    ...mapActions(["CreatePost", "GetPosts"]),
    async submit() {
      try {
        await this.CreatePost(this.form);
      } catch (error) {
        throw "Sorry you can't make a post now!"
      }
    },  
  }
};
</script>

We have an initial state for form, which is an object which has title and write_up as its keys and the values are set to an empty string. These values will change to whatever the user enters into the form in the template section of our component.

When the user submits the post, we call the this.CreatePost which receives the form object.

As you can see in the created lifecycle, we have this.GetPosts to fetch posts when the component is created.

Some styling,

<style scoped>
* {
  box-sizing: border-box;
}
label {
  padding: 12px 12px 12px 0;
  display: inline-block;
}
button[type=submit] {
  background-color: #4CAF50;
  color: white;
  padding: 12px 20px;
  cursor: pointer;
  border-radius:30px;
  margin: 10px;
}
button[type=submit]:hover {
  background-color: #45a049;
}
input {
  width:60%;
  margin: 15px;
  border: 0;
  box-shadow:0 0 15px 4px rgba(0,0,0,0.06);
  padding:10px;
  border-radius:30px;
}
textarea {
  width:75%;
  resize: vertical;
  padding:15px;
  border-radius:15px;
  border:0;
  box-shadow:0 0 15px 4px rgba(0,0,0,0.06);
  height:150px;
  margin: 15px;
}
ul {
  list-style: none;
}
#post-div {
  border: 3px solid #000;
  width: 500px;
  margin: auto;
  margin-bottom: 5px;;
}
</style>

2. Defining Routes

In our router/index.js file, import our views and define routes for each of them

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '../store';
import Home from '../views/Home.vue'
import Register from '../views/Register'
import Login from '../views/Login'
import Posts from '../views/Posts'

Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/register',
    name: "Register",
    component: Register,
    meta: { guest: true },
  },
  {
    path: '/login',
    name: "Login",
    component: Login,
    meta: { guest: true },
  },
  {
    path: '/posts',
    name: Posts,
    component: Posts,
    meta: {requiresAuth: true},
  }
]
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

3. Handling Users

  • Unauthorized Users
    If you noticed in defining our posts routes we added a meta key to indicate that the user must be authenticated, now we need to have a router.BeforeEach navigation guard that checks if a route has the meta: {requiresAuth: true} key. If a route has the meta key, it checks the store for a token; if present, it redirects them to the login route.
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})
router.beforeEach((to, from, next) => {
  if(to.matched.some(record => record.meta.requiresAuth)) {
    if (store.getters.isAuthenticated) {
      next()
      return
    }
    next('/login')
  } else {
    next()
  }
})

export default router
  • Authorized Users
    We also have a meta on the /register and /login routes. The meta: {guest: true} stops users that are logged in from accessing the routes with the guest meta.
router.beforeEach((to, from, next) => {
  if (to.matched.some((record) => record.meta.guest)) {
    if (store.getters.isAuthenticated) {
      next("/posts");
      return;
    }
    next();
  } else {
    next();
  }
});

In the end, your file should be like this:

import Vue from "vue";
import VueRouter from "vue-router";
import store from "../store";
import Home from "../views/Home.vue";
import Register from "../views/Register";
import Login from "../views/Login";
import Posts from "../views/Posts";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/register",
    name: "Register",
    component: Register,
    meta: { guest: true },
  },
  {
    path: "/login",
    name: "Login",
    component: Login,
    meta: { guest: true },
  },
  {
    path: "/posts",
    name: "Posts",
    component: Posts,
    meta: { requiresAuth: true },
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

router.beforeEach((to, from, next) => {
  if (to.matched.some((record) => record.meta.requiresAuth)) {
    if (store.getters.isAuthenticated) {
      next();
      return;
    }
    next("/login");
  } else {
    next();
  }
});

router.beforeEach((to, from, next) => {
  if (to.matched.some((record) => record.meta.guest)) {
    if (store.getters.isAuthenticated) {
      next("/posts");
      return;
    }
    next();
  } else {
    next();
  }
});

export default router;

4.Handling Expired Token (Forbidden Requests)

Our API is set to expire tokens after 30 minutes, now if we try accessing the posts page after 30 minutes, we get a 401 error, which means we have to log in again, so we will set an interceptor that reads if we get a 401 error then it redirects us back to the login page.

Add the snippet below after the Axios default URL declaration in the main.js file.

axios.interceptors.response.use(undefined, function (error) {
  if (error) {
    const originalRequest = error.config;
    if (error.response.status === 401 && !originalRequest._retry) {

        originalRequest._retry = true;
        store.dispatch('LogOut')
        return router.push('/login')
    }
  }
})

This should take your code to the same state as the example on GitHub.

Conclusion

If you've been able to follow along until the end, you should now be able to build a fully functional and secure front-end application. Now you’ve learned more about Vuex and how to integrate it with Axios, and also how to save its data after reloading.

Resources

Demystifying Django’s Magic

Demystifying Django’s Magic

Demystifying Django’s Magic

Precious Ndubueze

According to the official documentation,

Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.

DjangoProject.com

It is one of Python’s popular framework for building web applications and is most preferred for its scalability in creating a boilerplate with laid down configurations and files. This wraps up the whole goal of Django for developers;

“...building a great application in little time and less worry over basic settings.”

There are basic configurations, files and styling that cuts across all web application, no matter what you’re building. When using Django, these are generated for you automatically when you run certain commands at different points in the development cycle of your application. With this, you’ll have enough time to focus on the core aspect of your developing your application while Django handles the others for you.

It means that there are a lot of things happening underneath the hood. For someone who does not have a full grasp of the moving parts, this can be overwhelming. In this article we’re going to be learning about the following:

We’ll learn how these all fit together and the role they play. It’s important to know that it will not be possible to take a look at everything happening inside of Django. For cases outside the scope of this tutorial, there will be links to point you to resources that will help with that.

For learning purpose, we will be building an e-commerce platform. This is not a full-blown e-commerce site, our application will have only two basic features; creating products and making orders from the admin’s view.

This article will guide you through understanding Django’s pre-built commands, files and basics. It is targeted at developers who have used Django or intend to use it and want to know what happens under the hood. While it is not compulsory that you must have used Django, having experience with the basics of Django before continuing with this article will give you an advantage. You need to have a working knowledge of Python to fully grasp what will be covered in this piece.

Installing Django

If you do not have Django installed already, here are the steps to do that.

First, create a virtual environment, named djangodev. You need this to isolate your Django workspace and its dependent packages.

python3 -m venv ~/.virtualenvs/djangodev

Now to get into your virtual environment, you need to get it activated;

source ~/.virtualenvs/djangodev/bin/activate

If you get any error running with source you can make use of this alternative command,

. ~/.virtualenvs/djangodev/bin/activate

For Windows users, make use of this instead,

...\> %HOMEPATH%\.virtualenvs\djangodev\Scripts\activate.bat

Note: You need to activate the virtual environment anytime you want to use Django.

Now that you’re in your virtual env, install Django:

python -m pip install Django

To check if Django was properly installed, run;

python -m django --version

Shakaboom! Now you’re set for some magic, get your wands ready!

The method of creating files and folders at the go might be simple and straightforward. However, it is not ideal when you have to create a medium or large project and a deadline to beat. We always have a deadline to beat, even for personal projects.

Following this method when creating a new large project, you’ll have to create multiple files and folders and make multiple settings, which could lead to the creation of avoidable bugs and a lot of wasted time. startproject and startapp commands available to us through Django aim to solve this problem. These two commands create files, folders and carry out lots of configuration for you.

Let’s get started by generating a new application using the startproject command.

Scaffolding An Application

We’ll generate our application and take a look at the files that are generated, as well as few commands we’ll use. Run this command in your terminal to get started;

django-admin startproject ecommerce_site

When installing Django, django-admin is added to your path to give it access to perform its actions. It’s Django’s command-line utility for administrative responsibilities. Learn more django-admin uses here; it is a little beyond the scope of this article.

startproject is a Django command that helps generate the project. Here we pass to it the name of our project ecommerce_site. It then goes on to create the project in a directory called ecommerce_site. The directory should look like this,

shows directory after startproject.
Showing folder after running startproject. (Large preview)

We’ll need to navigate into the newly created directory and run the command to generate the app.

cd ecommerce_site
python manage.py startapp trading

startapp is a command executed by manage.py in the project directory, it creates multiple files, and a folder named using the name specified in the command, in this case, what’s specified is trading. The app folder consists of simple files that should be needed for creating features of an application. Now your directory should be looking like this:

shows directory after startapp.
Showing folder after running startapp. (Large preview)

If you’re wondering the difference between a project and an app. A project is a Django web application, it can consist of one or more apps. It’s the folder holding manage.py and the other module that includes the settings.py and other files. An app is a python package that does a certain feature, includes files like models.py, the migration directory and more. You can check out this answer on Stack Overflow for more.

The startproject will create the main project directory, while the startapp will create the app directory. Both are also been passed a name to be used in generation. The startproject is the first command run when creating a new project, while the startapp is run inside the new project directory.

The current layout is a great mapping you can use for whatever application you are building. It’s scalable and consists of files you will be needing. There is a chance to create, rename, edit and move files within the project.

Django’s Configuration

Django provides different settings and configurations for you, after running the two commands in different files, let’s run through the generated files to learn what each does.

settings.py

Most web applications need configurations like; authentication to verify users, permissions to allow certain users with certain abilities and set security standards. All these are needed to give your users the best experience needed. It will take a long time for you as a developer to begin to roll each of these on your own, for every application you work on.

Django provides you with these needed configurations and more — such that you can opt for the ones you want and plug them in. These are done in settings.py, there are already laid settings created to provide basic functionalities for you.

When Django tries to run a project, there are certain variables it looks for. The settings.py is the file holding those variables that lead to other files or information. Django is directed to this file from the manage.py file and takes information or loads other files it is led to from here.

These configurations include:

  • DEBUG
    The DEBUG functionality is set to True to enable reading of errors and debugging code in development, it should be set to False during production (i.e when you are deploying). This setting allows you to be able to debug your code better with the help of Django when it tries running your code.
  • INSTALLED_APPS
    This is a list of all apps performing a certain feature in Django. It comes with some defaults apps like the 'django.contrib.auth' which is used for user authentication, django.contrib.admin' for admin functionality, and 'django.contrib.messages' needed in sending notification or info to users. Any other app you create using the startapp command will have to be in here before it can be run by Django.
  • MIDDLEWARE is a lightweight framework for processing inputs and outputs going through Django requests and responses. Each middleware does a specific function with an app, like the 'django.contrib.auth.middleware.AuthenticationMiddleware' works with the 'django.contrib.sessions' to associate users with requests. You should check out further features of each middleware on the docs.
  • ROOT_URLCONF is a variable to a path where Django looks for your urlpatterns when a request is passed to your Django application. Whenever a request is made in a Django application, Django searches this variable and loads the leading file and loops though the urlpatterns list in search for the matching path.
  • TEMPLATES is a setting to allow rendering of HTML files to represent a view. It communicates with the request, auth and messages apps. The 'BACKEND' value and the 'APP_DIRS' set to True enables Django to search for HTML files within /templates folder. The 'context_processors' in the OPTIONS are callables, that take in the request as an argument and merge it with the context when a template is been rendered, these callables work to help with debugging, authentication, and handling of requests generally.
  • WSGI_APPLICATION is a path leading to the application variable in wsgi.py file.
  • DATABASE is a dictionary that holds access to an external database(or internal like in the case of the default sqlite) like PostgresSQL, MySQL and MongoDB. Here is an example of a Postgres database I will be using for our example:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'db_name',
        'USER': 'db_user',
        'PASSWORD': 'db_password',
        'HOST': 'localhost',
        'PORT': '',
    }
}

You can decide to use the default sqlite database, this doesn’t work great for production. You can set up a Postgres database like above, you will have to create the database first and pass the user, password with write permission to the database.

Note: For the purpose of this tutorial you can use the default sqlite database set already. The above is a demonstration on how you can set SQL and NO-SQL database connection in Django.

  • AUTH_PASSWORD_VALIDATORS these are functions that are called for password validations. They are called when creating users (and admins too) records, resetting and changing passwords. You can learn more about them from the official documentation.

More documentation on settings can be found here.

manage.py

Most web projects need an entry point where commands start from. An entry point acting as a gateway into the application. In every Django project, it is usually the manage.py file.

This file is needed as a starting point in your application. From here, all commands are run and everything starts up here.

manage.py does the same function as django-admin. It also tells Django the location for the configurations you have in your application. This is set by default and does not need to be changed.

It uses def main() function to handle the default settings module to our settings.py file. It tries to import the execute function, and if it runs into error it throws the ImportError. Otherwise, it takes in whatever argument is passed by the sys.argv from the command line and executes.

sys.argv is a list that takes in the command passed in via the terminal. When we run python manage.py runserver in the command line, sys.argv gets set to ["manage.py", "runserver"] and this is passed to the execute() function.

Arguments like python manage.py startapp trading or python manage.py makemigrations are set to [ "manage.py", "startapp", "trading"] and ["manage.py", "makemigrations"] respectively for execution.

The if statement is set to run if the command given set to manage.py.

Aside from running django-admin functions, you will be using it in running server, handle migrations, and other commands your project will be using. You will also need it when deploying, testing and debugging your projects.

Model Layout

Django provides a simple modelling layout for you. You configure your models in the models.py file in the trading directory. Your classes (or models) are layouts your database tables will follow.

You define your classes based on the data types you want to get from the user(or the admin) for each table. Our models create a migration file that works with the DATABASE variable in the settings.py.

Let’s create two models we’ll need, for products and orders:

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=50)
    image = models.ImageField(upload_to='products', default='python.png')
    price = models.IntegerField()
    description = models.TextField()

    def __str__(self):
        return self.name

class Order(models.Model):
    product =  models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    date = models.DateField(auto_now_add=True)
    user = models.CharField(max_length=50)

    def __str__(self):
        return self.user

The product model has a name, price, description and an image (which when saved will be uploaded to a products folder. This gets created if it folder doesn’t exist) columns. The order has a product (linked to the products table), the quantity, date and user columns.

With this information, Django creates a database schema for the app. Also, creates a Python database-access API for accessing Product and Order objects. Django has a built migration tool so you don’t have to use external packages for migrations.

Next step is to run the migration command and migrate to the database. But before we do that, we have to register trading as an app in our project directory.

First off, we have to configure the trading directory as an app in Django. Django has that done for you in the app.py file. This file registers trading as an app and will be further used in the settings.py file.

Now, in settings.py add trading in INSTALLED_APP. It should look like this:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'trading.apps.TradingConf',
]

On the terminal, run:

$ python manage.py makemigrations trading
$ python manage.py migrate

Note: Your virtual environment must be activated and you should be inside the project directory.

python manage.py makemigrations trading takes note of changes made in the trading module and creates a file in the migration folder of changes in models.py and python manage.py migrate then updates the changes.

You can find the generated migration file from the python manage.py makemigrations trading command in the migration directory named 0001_initial.py. You can see the classes and columns as commands for creation inside the file. As more changes in models.py are made, more files will be generated here.

Admin Features

In our current application, we’ll need admin features to manage the products and orders that will be created in the application. Django provides us with an in-built admin functionality already at our disposal. The admin app(feature) has also been registered in our INSTALLED_APP list; 'django.contrib.admin'.

Also, the URL for the admin has also been registered in the urlpatterns list our URL configuration file urls.py; path('admin/', admin.site.urls),. The admin.py file makes it possible for you to register models and the CRUD actions that are to be performed on the models.

To set up the admin, run this on the terminal:

$ python manage.py createsuperuser

You’ll be prompted to enter a username, email and password. After that, you’ve successfully created an admin. To get started with the admin functionality, run the server:

$ python manage.py runserver

Visiting http://127.0.0.1:8000/admin/ will lead to a login session, when you log in (with username and password you created) you are directed to admin dashboard:

this shows admin dashboard.
Admin dashboard provided by Django. (Large preview)

The groups and users are default configurations that allow admin to access user management and permission. Now let’s register our models.

In the admin.py, add the following lines:

from django.contrib import admin

# Register your models here.
from .models import Product, Order

admin.site.register(Product)
admin.site.register(Order)

from django.contrib import admin imports the admin functionality from Django, and then we imported the two classes from our models.py and registered them so an admin can perform CRUD actions on products and order from the dashboard.

After registering the models, your dashboard should look like this:

showing admin dashboard with products and order model.
Product and Order model on Admin Dashboard. (Large preview)

Adding products:

this shows admin admin adding a product to the site.
Admin adding a product(shoe) to site. (Large preview)

Making orders from admin dashboard:

Image showing admin making orders.
Admin adding/making orders. (Large preview)

Easy Routing

Every site needs route or URLs leading to specific pages with certain information for the end-user. Collections of similar information or application are accessed in a particular route. There is a need for grouping routes together for accessibility and communication within files. Django has that done for you in the urls.py file.

The urls.py is a file leading from the ROOT_CONF variable in the settings.py. This is where our main URL configuration is done. When we create views in different files or folders, or even in the urls.py file, they have to be configured in the urlpatterns list.

Django expects a list named urlpatterns in the file. You can change the file in the ROOT_CONF variable to any other file which you placed the urlpatterns list.

urlpatterns = [
    path('admin/', admin.site.urls),
]

The list consists of objects that are instances of path or re-path. Each instance has two compulsory parameters, the pattern 'admin/', and a view or URL file, admin.site.urls.

The instances could also be leading to another file with more URL configurations. This is done for readability.

views.py

An important need for a backend application is the views which each route maps to. In Django, you write your function or class based views for an app in the views.py file. A view is a class or function that processes a request and sends back a response to your user.

Edit your views.py file to this:

from django.http import HttpResponse

def home(request):
    return HttpResponse("This is a shopping site for products")

def order(request):
    return HttpResponse("Hello, welcome to our store, browse to our products and order!")

Here we imported HTTPResponse to use it in defining two views (function views) to return sentence when called on. def home should be called when you visit the primary address and def order when you access the order URL.

After creating our views we need to assign it to different routes(or urlpatterns). That can be done in several ways, we can create a urlpatterns list in the same file, or we could import trading.views into ecommerce_site.urls file and create the path.

The preferred way to do so is creating a sub URL configuration file (inside trading), and including it in ecommerce_site.urls. Create a urls.py file in the trading directory. Your directory should look like:

trading/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
        0001_initial.py
    models.py
    tests.py
    urls.py
    views.py

Add the following code to your new file (urls.py)

from django.urls import path

from . import views

urlpatterns = [
    path('', views.home, name='home'),
    path('order/', views.order, name='order'),
]

Let’s register the trading URL configuration file(urls.py) in the project URLs configuration file,

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('trading.urls'))
]

To be able to access the urlpatterns we will use the include class from django.urls in line 2. Then chose a pattern and attached the urls.py file in the trading directory.

What we did here was to import the path class from django, and the views.py file. Then our list consists of two path instances for each view. So def home is called on http://127.0.0.1:8000/.

image showing when you visit the site home.
Site when you visit home page. (Large preview)

And def order is called on http://127.0.0.1:8000/order/.

shows site when you visit order URL.
Order URL visited. (Large preview)

Note: The process of running startproject and startapp is not compulsory but highly encouraged by Django. And you can create/move files and folders after running each command to your working convenience.

Dependency Tree

Here is the process in which Django processes requests from URLs coming:

  1. Django searches for the root location (path) for your URLs in the ROOT_CONF variable.
  2. Django loads the python file given in the path provided. And locates the urlpatterns variable, this is a list containing all instances of django.urls.path() and/or django.urls.re_path().
  3. In the list, Django goes in order and looks for a matching URL.
  4. Python, imports and calls the view (either function-based or class-based view). Which is furthered passed with the HTTP request.
  5. The view processes the request and information passed and either returns a message/template or redirects to another view.
  6. If no URL pattern matches, or if an exception is raised during any point in this process, Django invokes an appropriate error-handling view.
Tree showing how Django handles requests.
Django request handling. (Large preview)

Conclusion

We have learnt of the possibility of creating files for Django without the command line. Also we have learnt the advantages and the building block of a Django Project. Django is an amazing tool in building backend projects with the goal to create simplicity.

You can check out the code for this tutorial on GitHub.

Resources

Smashing Editorial (ks, ra, yk, il)