In this chapter we’ll build a Django project that simply says “Hello, World” on the homepage. This is the traditional way to start a new programming language or framework. We’ll also work with Git for the first time and deploy our code to GitHub.

If you become stuck at any point, complete source code for this and all future chapters is available online on the official GitHub repo.

Initial Set Up

To begin, navigate to a new directory on your computer. For example, we can create a helloworld folder on the Desktop with the following commands.

$ cd ~/Desktop
$ mkdir helloworld && cd helloworld

Make sure you’re not already in an existing virtual environment at this point. If you see text in parentheses () before the dollar sign ($) then you are. To exit it, type exit and hit Return. The parentheses should disappear which means that virtual environment is no longer active.

We’ll use pipenv to create a new virtual environment, install Django, and then activate it.

$ pipenv install django~=3.1.0
$ pipenv shell

If you are on a Mac you should see parentheses now at the beginning of your command line prompt in the form (helloworld). If you are on Windows you will not see a visual prompt at this time.

Create a new Django project called config making sure to include the period (.) at the end of the command so that it is installed in our current directory.

(helloworld) $ django-admin startproject config .

If you use the tree command you can see what our Django project structure now looks like. (Note: If tree doesn’t work for you, install it with Homebrew: brew install tree.)

(helloworld) $ tree
├── Pipfile
├── Pipfile.lock
├── config
│   ├──
|   ├──
│   ├──
│   ├──
│   └──

1 directory, 8 files

The config/ file controls our project’s settings, tells Django which pages to build in response to a browser or URL request, and, which stands for Web Server Gateway Interface, helps Django serve our eventual web pages. The file is used to execute various Django commands such as running the local web server or creating a new app. Last, but not least, is the file, new to Django as of version 3.0 which allows for an optional Asynchronous Server Gateway Interface to be run.

Django comes with a built-in web server for local development purposes which we can now start with the runserver command.

(helloworld) $ python runserver

If you visit you should see our familiar Django welcome page.

Django welcome page

Note that the full command line output will contain additional information including a warning about 18 unapplied migrations.

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until
you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python migrate' to apply them.

August 3, 2020 - 14:57:42
Django version 3.1, using settings 'config.settings'
Starting development server at
Quit the server with CONTROL-C.

Technically this warning doesn’t matter at this point. Django is complaining that we have not yet “migrated,” or configured, our initial database. Since we won’t actually use a database in this chapter, the warning won’t affect the end result.

However, since warnings are still annoying to see, we can remove it by first stopping the local server with the Control+c command and then running python migrate.

$ python migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

What Django has done here is migrate the built-in apps provided for us which we’ll cover properly later in the book. But now, if you execute python runserver again, you should see the following clean output on the command line:

$ python runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
August 3, 2020 - 15:23:14
Django version 3.1, using settings 'config.settings'
Starting development server at
Quit the server with CONTROL-C.

Create An App

Django uses the concept of projects and apps to keep code clean and readable. A single Django project contains one or more apps within it that all work together to power a web application. This is why the command for a new Django project is startproject.

For example, a real-world Django e-commerce site might have one app for user authentication, another app for payments, and a third app to power item listing details: each focuses on an isolated piece of functionality. That’s three distinct apps that all live within one top-level project.

How and when you split functionality into apps is somewhat subjective, but in general, each app should have a clear function.

Now it’s time to create our first app. From the command line, quit the server with Control+c. Then use the startapp command followed by the name of our app, which will be pages.

(helloworld) $ python startapp pages

If you look again inside the directory with the tree command you’ll see Django has created a pages directory with the following files:

(helloworld) $ tree
├── pages
│   ├──
│   ├──
│   ├──
│   ├── migrations
│   │   └──
│   ├──
│   ├──
│   └──

Let’s review what each new pages app file does:

  • is a configuration file for the built-in Django Admin app
  • is a configuration file for the app itself
  • migrations/ keeps track of any changes to our file so our database and stay in sync
  • is where we define our database models which Django automatically translates into database tables
  • is for our app-specific tests
  • is where we handle the request/response logic for our web app

Even though our new app exists within the Django project, Django doesn’t “know” about it until we explicitly add it. In your text editor, open the file and scroll down to INSTALLED_APPS where you’ll see six built-in Django apps already there. Add our new pages app at the bottom:

# config/
    'pages', # new

Don’t worry if you are confused at this point: it takes practice to internalize how Django projects and apps are structured. Over the course of this book we will build many projects and apps and the patterns will soon become familiar.

URLs, Views, Models, Templates

In Django, at least three (often four) separate files are required to power one single page. Within an app these are the file, the file, the file, and finally an HTML template such as index.html.

This interaction is fundamental to Django yet very confusing to newcomers so let’s map out the order of a given HTTP request/response cycle. When you type in a URL, such as, the first thing that happens within our Django project is a URLpattern is found that matches the homepage. The URLpattern specifies a view which then determines the content for the page (usually from a database model) and then ultimately a template for styling and basic logic. The end result is sent back to the user as an HTTP response.

The complete flow looks something like this:

URL -> View -> Model (typically) -> Template

Remember how I said it can take three or four files for a given page? That’s because a model is not always needed, in which case three files are enough. But generally speaking four will be used as we’ll see later in this book.

The main takeaway here is that in Django views determine what content is displayed on a given page while URLConfs determine where that content is going. The model contains the content from the database and the template provides styling for it.

When a user requests a specific page, like the homepage, the file uses a regular expression to map that request to the appropriate view function which then returns the correct data. In other words, our view will output the text “Hello, World” while our url will ensure that when the user visits the homepage they are redirected to the correct view.

To see this in action, let’s start by updating the file in our pages app to look as follows:

# pages/
from django.http import HttpResponse

def homePageView(request):
    return HttpResponse('Hello, World!')

Basically, we’re saying whenever the view function homePageView is called, return the text “Hello, World!” More specifically, we’ve imported the built-in HttpResponse method so we can return a response object to the user. We’ve created a function called homePageView that accepts the request object and returns a response with the string “Hello, World!”

Now we need to configure our urls. Within the pages app, create a new file which on a Mac can be done with the touch command; Windows users must create the file within a text editor.

(helloworld) $ touch pages/

Then update it with the following code:

# pages/
from django.urls import path
from .views import homePageView

urlpatterns = [
    path('', homePageView, name='home')

On the top line we import path from Django to power our URLpattern and on the next line we import our views. By referring to the file as .views we are telling Django to look within the current directory for a file and import the view homePageView from there.

Our URLpattern has three parts:

  • a Python regular expression for the empty string ''
  • a reference to the view called homePageView
  • an optional named URL pattern called 'home'

In other words, if the user requests the homepage, represented by the empty string '', then use the view called homePageView.

We’re almost done at this point. The last step is to update our config/ file. It’s common to have multiple apps within a single Django project, like pages here, and they each need their own dedicated URL path.

Update the existing config/ file as follows:

# config/
from django.contrib import admin
from django.urls import path, include # new

urlpatterns = [
    path('', include('pages.urls')), # new

We’ve imported include on the second line next to path and then created a new urlpattern for our pages app. Now whenever a user visits the homepage they will first be routed to the pages app and then to the homePageView view.

This need for two separate files is often confusing to beginners. Think of the top-level config/ as the gateway to various url patterns distinct to each app.

Hello, World!

We have all the code we need now. To confirm everything works as expected, restart our Django server:

(helloworld) $ python runserver

If you refresh the browser for it now displays the text “Hello, world!”

Hello world homepage


In the previous chapter we also installed git which is a version control system. Let’s use it here. The first step is to initialize (or add) git to our repository.

(helloworld) $ git init

If you then type git status you’ll see a list of changes since the last git commit. Since this is our first commit, this list is all of our changes so far.

(helloworld) $ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)


nothing added to commit but untracked files present (use "git add" to track)

We next want to add all changes by using the command add -A and then commit the changes along with a message (-m) describing what has changed.

(helloworld) $ git add -A
(helloworld) $ git commit -m 'initial commit'

Please note Windows users may receive an error git commit error: pathspec ‘commit’ did not match any file(s) known to git which appears to be related to using single quotes '' as opposed to double quotes "". If you see this error, using double quotes for all commit messages going forward.


It’s a good habit to create a remote repository of your code for each project. This way you have a backup in case anything happens to your computer and more importantly, it allows for collaboration with other software developers. Popular choices include GitHub, Bitbucket, and GitLab. When you’re learning web development, it’s best to stick to private rather than public repositories so you don’t inadvertently post critical information such as passwords online.

We will use GitHub in this book but all three services offer similar functionality for newcomers. Sign up for a free account on GitHub’s homepage and verify your email address. Then navigate to the “Create a new repository” page located at

Enter the repository name hello-world and click on the radio button next to “Private” rather than “Public.” Then click on the button at the bottom for “Create Repository.”

GitHub new repository

Your first repository is now created! However there is no code in it yet. Scroll down on the page to where it says “…or push an existing repository from the command line.” That’s what we want.

GitHub Hello, World repository

Copy the text immediately under this headline and paste it into your command line. Note that my username is wsvincent here; yours will be different so if you copy my snippet below it won’t work! This syncs the local directory on our computer with the remote repository on the GitHub website.

(helloworld) $ git remote add origin

The last step is to “push” our code to GitHub.

(helloworld) $ git push -u origin master

Hopefully this command works and you can go back to your GitHub page and refresh it to see your local code now hosted online.

SSH Keys

Unfortunately, there is a good chance that the last command yielded an error if you are a new developer and do not have SSH keys already configured.

ERROR: Repository not found.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

This cryptic message means we need to configure SSH keys. This is a one-time thing but a bit of a hassle to be honest.

SSH is a protocol used to ensure private connections with a remote server. Think of it as an additional layer of privacy on top of username/password. The process involves generating unique SSH keys and storing them on your computer so only GitHub can access them.

First, check whether you have existing SSH keys. Github has a guide to this that works for Mac, Windows, and Linux. If you don’t have existing public and private keys, you’ll need to generate them. GitHub, again, has a guide on doing this.

Once complete you should be able to execute the git push -u origin master command successfully!

It’s normal to feel overwhelmed and frustrated if you become stuck with SSH keys. GitHub has a lot of resources to walk you through it but the reality is its very intimidating the first time. If you’re truly stuck, continue with the book and come back to SSH Keys and GitHub with a full nights sleep. I can’t count the number of times a clear head has helped me process a difficult programming issue.

Assuming success with GitHub, go ahead and exit our virtual environment with the exit command.

(helloworld) $ exit

You should now see no parentheses on your command line, indicating the virtual environment is no longer active.


Congratulations! We’ve covered a lot of fundamental concepts in this chapter. We built our first Django application and learned about Django’s project/app structure. We started to learn about views, urls, and the internal Django web server. And we worked with git to track our changes and pushed our code into a private repo on GitHub.

Continue on to Chapter 3: Pages App where we’ll build and deploy a more complex Django application using templates and class-based views.