I've had enough of the bullshit: the JS community sucks balls. All I wanted to do is build a simple, replicable Angular project with a Django back-end, but NO! That's not so simple when the community guiding your learning barely has any grasp on what it means to standardize.

If you're struggling, here's a guide to building a basic Angular app driven by Django and organized by Webpack.

Problem

I want to set up an Angular 1.1.x project and feed it data from a Django server. I'd like to use Django REST Framework (DRF) to build a RESTful API. I also want to bundle my JavaScript assets. For now, I plan to run the site on a single server.

Python 2.x

a virtual Python environment (See step 0)

Django 1.9.x ( pip install django )

) npm 2.15.8+

Webpack 1.13.x ( sudo npm i -g webpack )

) ESLint 2.13.1+ ( sudo npm i -g eslint )

) NodeJS 4.4.7+

Summary

Go!

Let's begin.

0. Set up your Python virtual environment

mkvirtualenv mysite

During this step, you should also make sure you install all of the pre-reqs, including Django.

1. Scaffold your project. Create your initial directories.

We want to focus on modularity throughout development. Therefore, there are lots of directories we'll end up utilizing. We want our directory tree to look like this initially:

mysite ├── backend │ ├── docs │ ├── requirements └── frontend ├── app │ ├── components │ └── shared ├── assets │ ├── css │ ├── img │ ├── js │ └── libs ├── config ├── dist └── js

Do it:

mkdir mysite && cd mysite mkdir -p backend/docs/ backend/requirements/ \ frontend/app/shared/ \ frontend/app/components/ \ frontend/config \ frontend/assets/img/ frontend/assets/css/ \ frontend/assets/js/ frontend/assets/libs/ \ frontend/dist/js/

*Note: This project structure was inspired by a few different projects. I find this organization ideal, but you don't have to. As you walk through this guide, you should stick to this structure so you don't encounter hiccups.

2. Scaffold your Django project.

Inside of the mysite/backend/ directory, create a Django project:

python django-admin.py startproject mysite

Also create your requirements.txt in requirements/ :

pip freeze > requirements/requirements.txt

Within the mysite/backend/mysite/ directory (your Django project), scaffold the directories in which your API will live:

mkdir -p applications/api/v1/ touch applications/__init__.py applications/api/__init__.py \ applications/api/v1/__init__.py applications/api/v1/routes.py \ applications/api/v1/serializers.py applications/api/v1/viewsets.py

Now, create your settings directory structure in mysite/backend/mysite/ :

mkdir -p mysite/settings/ touch mysite/settings/__init__.py \ mysite/settings/base.py mysite/settings/dev.py mysite/settings/prod.py \ mysite/dev.env mysite/prod.env

3. Set environment variables needed to run your Django server.

In this step, I prefer to use django-environ in order to manage my environment variables. There are many ways to do this, but the django-environ package simplified this process for me tremendously, so I use it in all of my projects.

Install django-environ :

pip install django-environ

In mysite/dev.env , add the following:

DATABASE_URL=sqlite:///mysite.db DEBUG=True FRONTEND_ROOT=path/to/mysite/frontend/ SECRET_KEY=_some_secret_key

We're going to use these environment variables in our settings. The benefit of having our environment-specific variables in separate files is mostly that such a setup provides ease as we switch between environments. In our case, the dev.env file is a list of variables we'd use in our local development environment. On the same token, our prod.env file contains production-specific variables.

*Note: The SECRET_KEY can be taken from the settings.py that was generated by django-admin.py startproject .

4. Install Django REST Framework and configure Django using your environment variables.

Install DRF:

pip install djangorestframework

Populate mysite/backend/mysite/settings/base.py with the following:

Tell Django where to find environment variables.

import environ project_root = environ.Path(__file__) - 3 env = environ.Env(DEBUG=(bool, False),) CURRENT_ENV = 'dev' # 'dev' is the default environment # read the .env file associated with the settings that're loaded env.read_env('./mysite/{}.env'.format(CURRENT_ENV))

Establish a database. In this case, we're going to use django-environ built-in SQLite settings.

DATABASES = { 'default': env.db() }

Specify a SECRET_KEY and the DEBUG state of the app.

SECRET_KEY = env('SECRET_KEY') DEBUG = env('DEBUG')

Add DRF to the pool of apps Django needs to utilize.

# Application definition INSTALLED_APPS = [ ... # Django Packages 'rest_framework', ]

Our URLs will live in URLs module generated with our base Django project.

ROOT_URLCONF = 'mysite.urls'

Tell Django where to find all of our templates and other static assets.

STATIC_URL = '/static/' STATICFILES_FINDERS = [ 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ] STATICFILES_DIRS = [ env('FRONTEND_ROOT') ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [env('FRONTEND_ROOT')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]

As per the TEMPLATES setting, Django should search for templates inside of your frontend/ directory. That's where your Angular app will live. We're only using Django to serve up the template within which our Angular app loads, which will be done via an entry-point directive. If you don't know what I mean, keep reading...

Populate settings/dev.py :

from mysite.settings.base import * CURRENT_ENV = 'dev'

Here, we tell Django that this settings file inherits settings from base.py and overrides the CURRENT_ENV constant string found in base.py . We're saying, "use this value instead of the value found in the module being inherited from."

5. Create an API.

We need something to test out our Angular services with, so let's build a small API. You can skip this, but I don't recommend you do that. Knowing that the Angular app setup worked entirely in terms of its potential to facilitate HTTP requests is important.

Generate a Django app. In mysite/backend/mysite

mkdir applications/games manage.py startapp games applications/games

Add your games app to your settings file of choice.

INSTALLED_APPS = [ ... 'applications.games' ]

Create a Django model in mysite/backend/mysite/applications/games/models.py

class Game(models.model): title = models.CharField(max_length=255) description = models.CharField(max_length=750)

Create a DRF serializer for the Game model in mysite/backend/mysite/applications/api/v1/serializers.py

from rest_framework.serializers import ModelSerializer from applications.games.models import Game class GameSerializer(ModelSerializer): class Meta: model = Game

Create a DRF viewset for your model in mysite/backend/mysite/applications/api/v1/viewsets.py

from rest_framework import viewsets from applications.games.models import Game from applications.api.v1.serializers import GameSerializer class GameViewSet(viewsets.ModelViewSet): queryset = Game.objects.all() serializer_class = GameSerializer

In mysite/backend/mysite/applications/api/v1/routes.py , register routes using DRF's router registration features.

from rest_framework import routers from applications.api.v1.viewsets import GameViewSet api_router = routers.SimpleRouter() api_router.register('games', GameViewSet)

Map URLs to registered DRF route inside of mysite/backend/mysite/mysite/urls.py :

from django.contrib import admin from django.conf.urls import include, url from applications.api.v1.routes import api_router urlpatterns = [ url(r'^admin/', admin.site.urls), # API:V1 url(r'^api/v1/', include(api_router.urls)), ]

6. Run your Django server using dev settings.

manage.py runserver --settings=mysite.settings.dev

By passing the DJANGO_SETTINGS_MODULE into runserver , we're telling Django to serve using specific settings.

If everything works, you should be able to go to localhost:8000/api/v1/games and see a DRF response. If you do, it's time to build an Angular app. If not, address your issues. If you're stuck, leave a comment and I'll help you out.

7. Initialize your npm package and install your front-end JS dependencies.

Your Angular app won't work the way we want it to without the right dependencies installed. It's time to install the minimum packages you'll need to get up and running.

Initialize your NPM package. From within mysite/frontend/ , run

npm init --yes

By passing the --yes flag into init , you're telling NPM to generate a package.json using NPM defaults. Otherwise, if you don't pass that in, you'll have to answer questions... Boring.

Install dev dependencies.

npm install --save-dev eslint eslint-loader

Install general dependencies.

npm install --save eslint eslint-loader angular angular-resource angular-route json-loader mustache-loader lodash

Make adjustments to package.json file in mysite/frontend/ so that it looks like the following:

{ "name": "my-app", "version": "0.0.1", "description": "This is my first angular app.", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "eslint": "^3.1.1", "eslint-loader": "^1.4.1" }, "dependencies": { "angular": "^1.5.8", "angular-resource": "^1.5.8", "angular-route": "^1.5.8", "eslint": "^3.1.1", "eslint-loader": "^1.4.1", "json-loader": "^0.5.4", "lodash": "^4.13.1", "mustache-loader": "^0.3.1" } }

Here's what you just installed:

eslint - a fancy linter, so we can keep our JavaScript neat (consistent).

- a fancy linter, so we can keep our JavaScript neat (consistent). eslint-loader - for running eslint via Webpack. I'll explain the concept of "loaders" in a bit.

- for running eslint via Webpack. I'll explain the concept of "loaders" in a bit. angular - an MVC framework. If you didn't know that, you should consider leaving this page.

- an MVC framework. If you didn't know that, you should consider leaving this page. angular-resource - our (Angular) HTTP library of choice. It's an abstraction of $http .

- our (Angular) HTTP library of choice. It's an abstraction of . json-loader - a loader (again, used by Webpack) for extracting JSON from .json files via require() throughout our app.

- a loader (again, used by Webpack) for extracting JSON from .json files via throughout our app. mustache-loader - a loader we'll use for parsing mustache templates. Mustache templates are fun.

It's safe for me to assume that you don't know how all of these packages will play together. Don't worry, bruh.

8. Create an Angular entry-point, declare initial dependencies, declare initial globals.

In mysite/frontend/app/app.js , add the following:

/* Libs */ require("angular/angular"); require("angular-route/angular-route"); require("angular-resource/angular-resource"); /* Globals */ _ = require("lodash"); _urlPrefixes = { API: "api/v1/", TEMPLATES: "static/app/" }; /* Components */ /* App Dependencies */ angular.module("myApp", [ "ngResource", "ngRoute", ]); /* Config Vars */ // @TODO in Step 13. /* App Config */ angular.module("myApp").config(routesConfig);

app.js is where Webpack will look for modules to bundle together. I personally appreciate this organization of declarations and method calls, but such an organization isn't necessary. There are 6 sections:

Libs - general libraries used throughout our Angular app

- general libraries used throughout our Angular app Globals - reserved global variables we can use throughout our app.

- reserved global variables we can use throughout our app. Components - project-specific modules

- project-specific modules App Dependencies - the declaration of our app entry point name and its dependencies

- the declaration of our app entry point name and its dependencies Config Vars - variables where configurations are stored, such as our route config

- variables where configurations are stored, such as our route config App Config - inject configs into our app using the stored configs from the previous section.

In order for the globals to work, you need to tell ESLint about which variables are global.

In mysite/frontend/config/eslint.json add the following:

{ "env": { "node": true }, "extends": "eslint:recommended", "rules": { "indent": [ "error", 2 ], "linebreak-style": [ "error", "unix" ], "quotes": [ "error", "double" ], "semi": [ "error", "always" ], "no-console": 0 }, "globals": { "_": true, "_urlPrefixes": true, "angular": true, "inject": true, "window": true }, "colors": true }

There are a few globals we've warned ESLint about here:

_ to represent lodash.

to represent lodash. _urlPrefixes which is an object we'll use throughout our app for URLs. I'll explain later.

which is an object we'll use throughout our app for URLs. I'll explain later. angular to represent the AngularJS object driving our entire application.

to represent the AngularJS object driving our entire application. inject which will be used for Angular dependency injections.

which will be used for Angular dependency injections. window which just represents the JavaScript window object, which is a representation of the DOM.

9. Configure Webpack.

Now that we've laid out most of our app dependencies, we can build our Webpack config file. Webpack will consolidate all of our dependencies, as well as app-specific modules we build, into one file, a bundle.

In mysite/frontend/webpack.config.js add the following.

module.exports = { entry: "./app/app.js", output: { path: "./dist/js/", filename: "bundle.js", sourceMapFilename: "bundle.js.map", }, watch: true, // eslint config eslint: { configFile: './config/eslint.json' }, module: { preLoaders: [{ test: /\.js$/, exclude: /node_modules/, loader: "eslint-loader" }], loaders: [ { test: /\.css$/, loader: "style!css" }, { test: /\.html$/, loader: "mustache-loader" }, { test: /\.json$/, loader: "json-loader" }] }, resolve: { extensions: ['', '.js'] } };

In order to have Webpack bundle our static dependencies together, we need to tell it where to look for those dependencies, which dependencies to process and how to manage them prior to bundling.

Let's have a quick look at what you're telling Webpack via webpack.config.js :

entry is the path to what Webpack needs to start bundling. This can either be a full path or a path that's relative to where webpack.config.js is located. In this case, it's the latter.

is the path to what Webpack needs to start bundling. This can either be a full path or a path that's relative to where is located. In this case, it's the latter. output is an object containing a path , which is the directory wherein the bundled dependencies should be placed; the filename is the name of the bundle; and, in this case, we decided to use sourceMapFilename to indicate what our source map should be called (which'll also be placed where our bundle is).

is an object containing a , which is the directory wherein the bundled dependencies should be placed; the is the name of the bundle; and, in this case, we decided to use to indicate what our source map should be called (which'll also be placed where our bundle is). watch tells Webpack to monitor file changes while it's running. If this isn't set to true , Webpack will run through its bundling process once and exit.

tells Webpack to monitor file changes while it's running. If this isn't set to , Webpack will run through its bundling process once and exit. eslint contains ESLint-specific settings, which are used by eslint-loader .

contains ESLint-specific settings, which are used by . module tells Webpack what to do with the modules it touches.

tells Webpack what to do with the modules it touches. module.preLoaders say what to do before bundling. In this case, we want to run our modules (excluding modules installed by npm) through eslint.

say what to do before bundling. In this case, we want to run our modules (excluding modules installed by npm) through eslint. module.loaders is where a loader sequence is specified. In our case, we only set test and loader , where test tells Webpack which modules to run a loader on (by matching the filename with a regex pattern), and loader tells Webpack which loader to use on the modules that match the regex pattern in test . Each loader is listed in a string and separated using an exclamation mark. Ex: loader!another_loader!yet_another_loader

is where a loader sequence is specified. In our case, we only set and , where tells Webpack which modules to run a loader on (by matching the filename with a regex pattern), and tells Webpack which loader to use on the modules that match the regex pattern in . Each loader is listed in a string and separated using an exclamation mark. Ex: module.preLoaders say which preLoaders to run modules through. The settings we use are just like the settings we wrote in module.loaders .

But Greg, what the fuck's the difference between preLoaders and loaders? I'm glad you asked, my swearing friend!!

A preLoader processes code prior to loaders, for instance, to lint your JavaScript modules.

A loader tells Webpack how to bundle require()d files. A loader looks at a module, says, "Hey, as you pack this into one file as a string, this is how it should be transformed for the bundle."

A postLoader is a Webpack plugin that processes code after it's already been bundled. We haven't specified any postLoaders for the sake of simplicity.

10. Tell Django to load your app.

Right now, all you've managed to do is tell Webpack what to create and how to create what needs to be created. (Hell, at this point, I'd be surprised if you tried to run Webpack and it runs without error. If it does, I'm the fuckin' man.)

Since Django imposes its own URL processor on our application, we're mostly at the mercy of Django for managing what's entered into a user's browser location bar. However, we're building a single-page application using a totally different framework, and we want that application to have full control of the user's input. All we need Django for is to serve the single page on which our SPA is running. Therefore, ...

In mysite/backend/mysite/mysite/urls.py add the following in the urlpatterns list

# Web App Entry url(r'^$', TemplateView.as_view(template_name="app/index.html"), name='index'),

That says that when a user goes to mysite.com/ , env('FRONTEND_ROOT') + app/index.html will be searched for by STATICFILES_FINDERS in order to render a generic-looking HTML template.

11. Create the Angular app base template.

Our mysite/frontend/app/components/app/index.html template should look like a regular Django template.

In mysite/frontend/app/index.html , add the following:

{% load staticfiles %} <!DOCTYPE html> <html ng-app="myApp"> <head> <title>My Site</title> <script src="{% static 'dist/js/bundle.js' %}"></script> </head> <body> </body> </html>

At this point, you should be able to run Webpack. If you run your Django server and go to localhost:8000 , you should see a blank page. If not, let me know.

12. Write a home component.

Let's write our first component. It'll display text on our page when a user goes to localhost:8000 .

Create a component directory and base files. In frontend/app/components/ :

mkdir home && touch home/home-controller.js home/home.js home/home.html

In mysite/frontend/app/components/home/home.html , add the following:

<div ng-controller="HomeController as ctrl"> <div> <h1>Home!</h1> </div> </div>

Now, add the following to mysite/frontend/app/components/home/home-controller.js :

function HomeController() { var that = this; that.foo = "Foo!"; console.log(that); // should print out the controller object } angular.module("Home") .controller("HomeController", [ HomeController ]);

*Learn why NOT to do var that = this; over at my friend's blog!

Your Angular module definition should be declared in home.js :

angular.module("Home", []); require("./home-controller");

Now, we can refer to "Home" in a module definition's dependency space. Let's do that.

In mysite/frontend/app/app.js add the following:

/* Components */ require("./components/home/home"); /* App Dependencies */ angular.module("myApp", [ "Home", // this is our component "ngResource", "ngRoute" ]);

13. Write Angular routes leading to your home component and a 404 page.

We need to configure our first route. When a user goes to localhost:8000 , Angular should take control upon loading the Django template that's rendered. To do this, we're going to leverage angular-router .

In mysite/frontend/app/routes.js , write the following:

function routesConfig($routeProvider) { $routeProvider .when("/", { templateUrl: _urlPrefixes.TEMPLATES + "components/home/home.html", label: "Home" }) .otherwise({ templateUrl: _urlPrefixes.TEMPLATES + "404.html" }); } routesConfig.$inject = ["$routeProvider"]; module.exports = routesConfig;

If we don't add _urlPrefixes.TEMPLATES , angular-router will assume that components/home/home.html is an actual URL the server recognizes. Because of STATIC_URL in our Django settings, assuming localhost:8000/components/home/home.html will work is incorrect.

Also, if you haven't already, you'll notice otherwise({...}) in the routes code. That's how 404s will be handled.

In mysite/frontend/app/404.html , put the following:

<h1>NOT FOUND</h1>

Finally, in mysite/frontend/app/app.js , add the following:

/* Config Vars */ var routesConfig = require("./routes");

14. Add angular-router directives to the app entrypoint template.

Now, we need to tell Angular where the switching of views will happen as a user navigates. In order to do this, we use more of angular-router 's power.

In the <head> tags of mysite/frontend/app/index.html , add:

<base href="/">

Now, in the <body> tags, add:

<div ng-view></div>

Your index.html should look like this:

{% load staticfiles %} <!DOCTYPE html> <html ng-app="myApp"> <head> <title>My Site</title> <script src="{% static 'dist/js/bundle.js' %}" ></script> <base href="/"> </head> <body> <div> <div ng-view></div> </div> </body> </html>

Run Webpack. Go to localhost:8000 . You should see what's in home/home.html . (If not, let me know!)

15. Try out your REST API in your Angular app.

If everything's gone right up to this point, you should be able to write Angular services for your Django API. Let's build a small component to see if we can do that. This component should list games. I assume you've already populated your database so that an HTTP request to localhost:8000/api/v1/games will return a list of games.

Create a component scaffold in frontend/app/components/ :

mkdir -p game/list/ && touch game/list/game-list-controller.js game/list/game-list-controller_test.js game/game-service.js game/game.js game/game.html

This component should list games. I assume you've already populated your database so that an HTTP request to localhost:8000/api/v1/games will return a list of games.

In game/game-service.js :

function GameService($resource) { /** * @name GameService * * @description * A service providing game data. */ var that = this; /** * A resource for retrieving game data. */ that.GameResource = $resource(_urlPrefixes.API + "games/:game_id/"); /** * A convenience method for retrieving Game objects. * Retrieval is done via a GET request to the ../games/ endpoint. * @param {object} params - the query string object used for a GET request to ../games/ endpoint * @returns {object} $promise - a promise containing game-related data */ that.getGames = function(params) { return that.GameResource.query(params).$promise; }; } angular.module("Game") .service("GameService", ["$resource", GameService]);

Notice the reference to $resource , which is what we're using in order to set up our HTTP mechanisms in our service.

In game/list/game-list-controller.js :

function GameListController(GameService) { var that = this; /* Stored game objects. */ that.games = []; /** * Initialize the game list controller. */ that.init = function() { return GameService.getGames().then(function(games) { that.games = games; }); }; } angular.module("Game") .controller("GameListController", [ "GameService", GameListController ]);

In game/game.html :

<div ng-controller="GameListController as ctrl" ng-init="ctrl.init()"> <div> <h1>Games</h1> <!-- Test List --> <ul> <li ng-repeat="game in ctrl.games">{{ game.title }}</li> </ul> </div> </div>

In game/game.js :

angular.module("Game", []); require("./list/game-list-controller"); require("./game-service");

Next, refer to the component in app.js :

/* Components */ require("./components/game/game"); /* App Dependencies */ angular.module("myApp", [ "Home", "Game", "ngResource", "ngRoute" ]);

Finally, we're going to have to configure routes for our list of games, so in mysite/frontend/app/routes.js , add the following to the $routeProvider object:

.when("/game", { templateUrl: _urlPrefixes.TEMPLATES + "components/game/list/game-list.html", label: "Games" })

Run Webpack again. Everything should compile correctly. If not, let me know.

Go to localhost:8000/#/games and you should see a list of games.

Done

That's that.

Concerns/Thoughts

There are some concerns here:

Environment variables can fuck you up if you don't know how to manage them. Although the way we're working with environment variables here works locally, it may not be the right way to go in production. You can make it work, though, as the method described is fairly robust, AFAIK. Just, don't throw secrets into your .env files.

can fuck you up if you don't know how to manage them. Although the way we're working with environment variables here works locally, it may not be the right way to go in production. You can make it work, though, as the method described is fairly robust, AFAIK. Just, don't throw secrets into your files. Your Angular app is tightly coupled with Django . This won't make your project composable between the front-end and back-end. If you rip Django out of the picture, your routes will have to change, unless you configure your new backend to behave the same way that staticfiles works. You'll also need to change the index.html file where your Angular entrypoint lives. There isn't much work involved when breaking away from Django on a small project. Larger projects with a lot more coupling could make for a bad time, though. My advice: the only coupling between your Angular app and Django server should be the index entry-point.

. This won't make your project composable between the front-end and back-end. If you rip Django out of the picture, your routes will have to change, unless you configure your new backend to behave the same way that works. You'll also need to change the file where your Angular entrypoint lives. There isn't much work involved when breaking away from Django on a small project. Larger projects with a lot more coupling could make for a bad time, though. My advice: the only coupling between your Angular app and Django server should be the index entry-point. Deployment should be done the same way any Django app deployment should be.

That's about it. If you have absolutely ANY questions or you encounter some sorta hiccup, please, please, please let me know in the comments below. That way, I can improve this guide for future readers.

>>>Cheat<<<

I'll share a GitHub repo with all of this code in it soon...

I miss you, Renee. Let's go.