Django & EmberJS Full Stack Basics: Connecting Frontend and Backend — Part 4

Michael X
10 min readAug 28, 2018

--

In Part 4 we shift our attention to the frontend and begin working with Ember. We’ll install the required software, setup a basic DOM, styles, create the book model, and the books route. We’ll also load up fake book data for demonstration purposes before we go on to access real data from the backend.

P1, P2, P3, P4, P5

4.1 Install Software

Before we begin frontend development we need to install some software:

4.1.1 NodeJS and NPM

The most straightforward method of installing NodeJS and NPM is using the installation file from the official site.

Once installation is complete check that everything is installed:

node --version
npm --version

4.1.2 Ember CLI

Let’s use NPM to install the Ember CLI, ‘the official command line utility used to create, build, serve, and test Ember.js apps and addons’ :

# install Ember CLI
npm install -g ember-cli
# check that it's installed
ember --version

4.2 Ember Project

4.2.1 Create a new Ember project

Let’s create a frontend client called client:

# cd into the main project folder
cd ~/desktop/my_library
# create a new app: client
ember new client
# cd into the directory
cd 'client'
# run the server
ember s

Head over to http://localhost:4200 and you should see this screen:

Up and running

Shutdown the server with ctrl+C.

4.2.2 Update .gitignore with Ember exclusions

Before we make any new commits, let’s update the .gitignore file to exclude unwanted files from being pushed to the repo. Add on to the file below the Django section:

...### Ember ###
/client/dist
/client/tmp
# dependencies
/client/node_modules
/client/bower_components
# misc
/client/.sass-cache
/client/connect.lock
/client/coverage/*
/client/libpeerconnection.log
/client/npm-debug.log
/client/testem.log
# ember-try
/client/.node_modules.ember-try/
/client/bower.json.ember-try
/client/package.json.ember-try

4.3 Display books data

4.3.1 Setup the DOM

Now that we have the software installed let’s set up a basic DOM and styles. I’m not doing anything fancy here, just the bare minimum to have our data displaying in a readable format.

Locate the file client/app/templates/application.hbs. Get rid of {{welcome-page}} and the comments .

Then create a div with the class .nav. Use Ember’s built-in {{#link-to}} helper to create a link to the route books(we will create it later):

<div class="nav">
{{#link-to 'books' class="nav-item"}}Home{{/link-to}}
</div>

Wrap everything including the{{outlet}} in a div with the .container class. Each route template will be rendered inside {{outlet}}:

<div class="container">
<div class="nav">
{{#link-to 'books' class="nav-item"}}Home{{/link-to}}
</div>
{{outlet}}
</div>

Since this is the template for the parent level application route any sub-routes like books will be rendered inside the {{outlet}}. This means that the nav will always be visible on screen.

4.3.2 Create styles

I’m not going to get into the nitty gritty of the CSS. It’s pretty simple to figure out. Locate the file client/app/styles/app.css and add the following styles:

Variables and Utilities

:root {
--color-white: #fff;
--color-black: #000;
--color-grey: #d2d2d2;
--color-purple: #6e6a85;
--color-red: #ff0000;
--font-size-st: 16px;
--font-size-lg: 24px;
--box-shadow: 0 10px 20px -12px rgba(0, 0, 0, 0.42),
0 3px 20px 0px rgba(0, 0, 0, 0.12),
0 8px 10px -5px rgba(0, 0, 0, 0.2);
}
.u-justify-space-between {
justify-content: space-between !important;
}
.u-text-danger {
color: var(--color-red) !important;
}

General

body {
margin: 0;
padding: 0;
font-family: Arial;
}
.container {
display: grid;
grid-template-rows: 40px calc(100vh - 80px) 40px;
height: 100vh;
}

Navigation

.nav {
display: flex;
padding: 0 10px;
background-color: var(--color-purple);
box-shadow: var(--box-shadow);
z-index: 10;
}
.nav-item {
padding: 10px;
font-size: var(--font-size-st);
color: var(--color-white);
text-decoration: none;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}

Headings

.header {
padding: 10px 0;
font-size: var(--font-size-lg);
}

Books List

.book-list {
padding: 10px;
overflow-y: scroll;
}
.book {
display: flex;
justify-content: space-between;
padding: 15px 10px;
font-size: var(--font-size-st);
color: var(--color-black);
text-decoration: none;
cursor: pointer;
}
.book:hover {
background: var(--color-grey);
}

Buttons

button {
cursor: pointer;
}

Book Detail

.book.book--detail {
flex-direction: column;
justify-content: flex-start;
max-width: 500px;
background: var(--color-white);
cursor: default;
}
.book-title {
font-size: var(--font-size-lg);
}
.book-title,
.book-author,
.book-description {
padding: 10px;
}

Add/Edit Book Form

.form {
display: flex;
flex-direction: column;
padding: 10px 20px;
background: var(--color-white);
}
input[type='text'],
textarea {
margin: 10px 0;
padding: 10px;
max-width: 500px;
font-size: var(--font-size-st);
border: none;
border-bottom: 1px solid var(--color-grey);
outline: 0;
}

Actions

.actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 10px 20px;
background-color: var(--color-white);;
box-shadow: var(--box-shadow)
}

4.4 The books route

4.4.1 Create the books route

Now that we have our basic styles and container DOM in place let’s generate a new route that will display all of the books in our database:

ember g route books

The router file client/app/router.js will now be updated with:

import EmberRouter from '@ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('books');
});
export default Router;

4.4.3.2 Load fake data in the model hook

Let’s edit the books route client/app/routes/books.js to load all books from the database.

import Route from '@ember/routing/route';export default Route.extend({  model() {
return [
{
title: 'Monkey Adventure',
description: 'A tale of high seas monkey adventure.'
},
{
title: 'Island Strife',
description: 'Life in paradise takes a turn for Monkey.'
},
{
title: 'The Ball',
description: 'Monkey encounters a ball. What to do?'
},
{
title: 'Simple Pleasures of the South',
description: 'Monkey reminisces on a life well lived.'
},
{
title: 'Big City Monkey',
description: 'Toronto! Tall buildings and busy people!'
}
]
}
});

The model hook is returning an array of objects. This is fake data for demonstration purposes. We will come back here later and load the actual data from the database using Ember Data when we are ready.

4.4.3.3 Update the books route template

Let’s edit the books route template client/app/templates/books.hbs to display the books that are returned in the model.

<div class="book-list">
{{#each model as |book|}}
<div class="book">
{{book.title}}
</div>
{{/each}}
</div>

Ember uses the Handlebars Template Library. Here we use the each helper to iterate through our array of books data in model. We wrap each of the items in the array in a div with the class book and access and display it’s title information with book.title.

4.4.4 Demonstration: books route loading and displaying fake data

Now that we have the DOM, book model, and books route setup with some fake data we can see this running in the browser. Visit localhost:4200/books and you should see this:

Mouse over a book title to highlight

4.4.5 Create application route for redirect

It’s kind of annoying to have to put a /books to visit the books route. Let’s generate the application route and use the redirect hook to redirect to the books route when we enter the base route.

ember g route application

If prompted to overwrite the application.hbs template, say no. We don’t want to overwrite the template we already setup.

In client/app/routes/application.js create the redirect hook:

import Route from '@ember/routing/route';export default Route.extend({
redirect() {
this.transitionTo('books');
}
});

If you visit localhost:4200 it will now redirect to localhost:4200/books.

4.5 Display books route with real data

4.5.1 Create an application adapter

We don’t want to use fake data forever so let’s connect to the backend using and adapter and start pulling the books data into the client. Think of adapters as an “object that receives requests from a store and translates them into the appropriate action to take against your persistence layer… usually an HTTP API.

Generate a new application adapter:

ember g adapter application

Locate the file client/app/adapters/application.js and update it:

import DS from 'ember-data';
import { computed } from '@ember/object';
export default DS.RESTAdapter.extend({
host: computed(function(){
return 'http://localhost:8000';
}),
namespace: 'api'
});

The JSONAPIAdapter is the ‘default adapter used by Ember Data’. It transforms the store’s requests into HTTP requests that follow the JSON API format. It plugs into the data management library called Ember Data. We use Ember Data to interface with the backend in a more efficient way. It can store and manage data in the frontend and make requests to the backend when required to update instead of making constant requests to the backend. This helps the user experience feel more fluid with generally faster loading times.

We will use it’s built-in store service to easily access server data without writing more complex ajax or fetch requests (though those are still necessary for more complex use cases).

Here the adapter is telling Ember Data that it’s host may be found at http://localhost:8000 namespaced to api. This means that any calls to the server will be understood to start with http://localhost:8000/api/.

4.5.2 Create the book model

Since we are working with Ember Data, we’ll have to generate a book model so it understands what the data coming from the backend should map to:

ember g model book

Locate the file in client/models/book.js and define the book model:

import DS from 'ember-data';export default DS.Model.extend({
title: DS.attr(),
author: DS.attr(),
description: DS.attr()
});

The attributes are the same as those we’ve defined in the backend. We define them again so that Ember Data knows what to expect from the structured data.

4.5.3 Update the books route

Let’s update the books route by importing the store service and using it to request data.

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
store: service(),

model() {
const store = this.get('store');
return store.findAll('book');
}
});

4.5.4 Demonstration: books has a CORS issue

Now that we’ve created an application adapter and updated the books route to query for all books in the data base, let’s see what we’re getting back.

Run both the Django and Ember servers then visit localhost:4200/books and you should see this in the console:

There seems to be a problem with CORS.

4.5.5 Resolve the Cross-Origin Resource Sharing (CORS) issue

CORS ‘defines a way in which a browser and server can interact to determine whether or not it is safe to allow the cross-origin request’. To keep things simple we are making a request from localhost:4200 (our frontend) to the server at localhost:8000/api/books in order to capture our books data. The request is essentially blocked because our frontend is not an allowed origin from which to request the data from our backend endpoint. We can resolve this issue by allowing all requests to pass through during development.

Begin by installing an app that adds CORS headers to responses:

pip install django-cors-headers

Install it into server's settings.py file under the INSTALLED_APPS array:

INSTALLED_APPS = [
...
'books',
'corsheaders'
]

Add it to the top of the MIDDLEWARE array:

MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]

Finally, allow all requests to get through during development:

CORS_ORIGIN_ALLOW_ALL = DEBUG

4.5.6 Demonstration: CORS issue resolved, incompatible data format

Visit localhost:4200 and you should see this in the console:

Looks like we solved the CORS issue and we’re receiving a response from server with the data that we expect:

[
{
"id": 1,
"title": "Conquistador",
"author": "Buddy Levy",
"description": "It was a moment unique in ..."
},
{
"id": 2,
"title": "East of Eden",
"author": "John Steinbeck",
"description": "In his journal, Nobel Prize ..."
}
]

We do get an array of objects in JSON format, unfortunately it’s still not in quite the shape we expect it to be. This is what Ember Data expects:

{
data: [
{
id: "1",
type: "book",
attributes: {
title: "Conquistador",
author: "Buddy Levy",
description: "It was a moment unique in ..."
}
},
{
id: "2",
type: "book",
attributes: {
title: "East of Eden",
author: "John Steinbeck",
description: "In his journal, Nobel Prize ..."
}
}
]
}

Close but not quite there yet.

4.6 Conclusion

We’ve completed the following steps:

  • Installed NodeJS and NPM
  • Installed the Ember CLI and created a new client project
  • Basic DOM setup
  • Created a books route and template to load and display books
  • Demonstrated the app running with fake data
  • Created an application adapter to connect to the backend and receive data
  • Created a book model and updated the books route to capture backend data
  • Demonstrated that the data we are currently getting from the backend isn’t structured in the way that Ember Data expects it to be

In Part 5 we will go back to Django and structure the data (using the Django REST Framework JSON API library) in a way that works with Ember Data. We’ll also update the backend API to return book detail data so that we can look at a single instance of a book record.

--

--

Michael X

Software Developer at Carb Manager | lookininward.github.io | carbmanager.com | Hiring Now! | @mxbeyondborders | /in/vinothmichaelxavier