Ember Project Structure — Understanding the Default Layout
In this tutorial, you will learn about Ember Project Structure. We cover key concepts, practical examples, and best practices to help you master this topic.
Ember's project structure follows strict conventions. Every file has a specific location based on its purpose. The resolver uses these conventions to automatically wire routes to templates, components to templates, and models to routes without manual imports.
What You'll Learn
You will understand every directory and file in an Ember project, how the resolver maps names to files, and how to organize code following Ember best practices.
Why It Matters
Ember conventions mean every project looks the same. When you join a new Ember team, you already know where files live. This consistency is one of Ember's strongest advantages for team productivity.
Real-World Use
A team of 15 developers maintains an Ember application with 200+ routes. New developers become productive in days because the project structure is predictable. Every route is in app/routes/, every component in app/components/.
flowchart TD
A[app/] --> B[components/]
A --> C[controllers/]
A --> D[helpers/]
A --> E[models/]
A --> F[routes/]
A --> G[services/]
A --> H[styles/]
A --> I[templates/]
A --> J[router.js]
A --> K[app.js]
The app/ Directory
This is where all application code lives. Everything inside app/ is automatically resolved by name.
app/
components/ # Reusable UI components
controllers/ # Route controllers (optional with Ember Octane)
helpers/ # Template helper functions
models/ # Ember Data models
routes/ # Route handlers
services/ # Singleton services
styles/ # Component CSS
templates/ # Handlebars templates
app.js # Application entry point
index.html # HTML shell
router.js # Route map
The app.js Entry Point
// app/app.js
import Application from '@ember/application';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';
export default class App extends Application {
modulePrefix = config.modulePrefix;
podModulePrefix = config.podModulePrefix;
Resolver = Resolver;
}
loadInitializers(App, config.modulePrefix);
The router.js File
This file defines the route map. Routes are nested using closures.
// app/router.js
import EmberRouter from '@ember/routing/router';
import config from './config/environment';
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
Router.map(function() {
this.route('about');
this.route('contact');
this.route('posts', function() {
this.route('post', { path: ':post_id' });
});
this.route('admin', function() {
this.route('users');
this.route('settings');
});
});
Routes Directory
Each route file handles data loading for its template.
// app/routes/about.js
import Route from '@ember/routing/route';
export default class AboutRoute extends Route {
model() {
return {
title: 'About Us',
team: ['Alice', 'Bob', 'Charlie']
};
}
}
Templates Directory
Templates use Handlebars syntax. Each route has a corresponding template.
{{! app/templates/about.hbs }}
<div class="about-page">
<h1>{{this.model.title}}</h1>
<h2>Our Team</h2>
<ul>
{{#each this.model.team as |member|}}
<li>{{member}}</li>
{{/each}}
</ul>
<LinkTo @route="contact">Contact Us</LinkTo>
</div>
Components Directory
Components have a JavaScript file and a template file.
// app/components/nav-bar.js
import Component from '@glimmer/component';
export default class NavBarComponent extends Component {
get isLoggedIn() {
return !!this.args.session?.user;
}
}
{{! app/components/nav-bar.hbs }}
<nav class="nav-bar">
<LinkTo @route="index">Home</LinkTo>
<LinkTo @route="about">About</LinkTo>
{{#if this.isLoggedIn}}
<LinkTo @route="profile">Profile</LinkTo>
<button type="button" {{on "click" this.args.logout}}>Logout</button>
{{else}}
<LinkTo @route="login">Login</LinkTo>
{{/if}}
</nav>
Models Directory
Ember Data models define attributes and relationships.
// app/models/post.js
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';
export default class PostModel extends Model {
@attr('string') title;
@attr('string') body;
@attr('date') createdAt;
@belongsTo('user') author;
@hasMany('comment') comments;
}
Services Directory
Services are singletons shared across the application.
// app/services/session.js
import Service from '@ember/service';
export default class SessionService extends Service {
user = null;
login(email, password) {
// Authentication logic
}
logout() {
this.user = null;
}
get isAuthenticated() {
return !!this.user;
}
}
Config Directory
// config/environment.js
module.exports = function(environment) {
let ENV = {
modulePrefix: 'my-ember-app',
environment: environment,
rootURL: '/',
locationType: 'history',
EmberENV: {
EXTEND_PROTOTYPES: false,
FEATURES: {}
},
APP: {}
};
if (environment === 'production') {
// Production-specific config
}
return ENV;
};
Common Mistakes
- Putting files in wrong directories.
routes/about.jsis a route handler.components/about.jsis a component. The resolver treats them differently. - Forgetting to update
router.jsafter creating routes manually. Always useember generate routeto keep the router in sync. - Creating controllers when not needed. Ember Octane (3.15+) makes controllers optional. Use components with
@trackedproperties instead. - Storing templates in the wrong location. Templates for routes go in
app/templates/. Templates for components go inapp/components/. - Editing
app/index.htmlunnecessarily. app/index.html is the HTML shell. Most content belongs in templates.
Practice Questions
- What is the purpose of
router.js? - Where do route handler files live?
- How does Ember resolve which template to render for a route?
- What is the difference between a Service and a Component?
- Challenge: Create an Ember project with routes for
articles,articles/article(with dynamic segment), andprofile. Add a service calleduser-preferences. Add a component calledarticle-card. Verify the resolver loads all files correctly.
FAQ
Mini Project
Create a new Ember project. Generate four routes: home, products, products/product, and cart. Generate a component called product-card and a service called shopping-cart. Write the route handler for products that returns a list of products. Wire the navigation in application.hbs.
What's Next
Now that you understand the project structure, learn about Ember Routes for handling URL-based navigation. Then explore Ember Route Hooks for data loading.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro