Why Ember.js Still Makes Sense for Big Teams Building Big Apps

In a JavaScript world dominated by React’s flexibility and Vue’s friendliness, Ember.js can feel like the quiet older sibling who keeps showing up to work in a suit. It’s opinionated, batteries-included, and unapologetically convention-driven. Which is exactly why some of the largest engineering teams in the world — LinkedIn, Apple Music’s web UI, Intercom, Square’s dashboard, Discourse — still bet on it. 🔥

What Is Ember.js?

Ember is a framework, not a library. The distinction matters. React gives you a rendering primitive and lets you pick the router, state manager, testing harness, build tool, data layer, and folder layout yourself. Ember gives you all of that in one box: router, components, data layer (Ember Data), testing framework (QUnit), build pipeline (Ember CLI), and a strong convention for where every file goes.

It’s the same philosophy as Ruby on Rails: convention over configuration. If you accept the conventions, you write less code and onboard new engineers faster. If you fight the conventions, you have a bad time.

Why Ember? (And Why Now?)

The case for Ember is rarely “it’s the fastest” or “it has the smallest bundle” — those are not its strengths. The case is about cost over time:

  • Stability without stagnation — Ember follows a six-week release cadence with strict semver and a deprecation pipeline. Upgrades from version to version are mostly mechanical, not rewrites. Long-running apps survive 5+ years on Ember without the “big rewrite” you see in React projects every 18 months.
  • One way to do things — there’s a canonical place for routes, components, models, services, and adapters. New hire on day one knows where to look.
  • First-class testing — every ember-cli generator scaffolds a test file alongside the source. Unit, integration, and acceptance tests are first-class citizens, not an afterthought.
  • Ember Data — a JSON:API client baked in. Identity-mapped, cached, with relationships and dirty tracking out of the box. You don’t roll your own data layer.

Is It Better for Bigger Teams?

Yes — and that’s not a coincidence, it’s the design goal. Ember’s opinions act as a coordination tax that pays back at scale.

On a 5-person React team, freedom is a feature. Everyone knows the codebase, conventions emerge organically, and the team can pivot when something better comes along. On a 50-person Ember team across multiple squads, freedom becomes friction. Squad A’s state management decision becomes Squad B’s onboarding pain. Ember sidesteps that whole class of debate — there is a state management story (@tracked, services), there is a routing story, there is a data layer story. Squads argue about features instead of stacks.

This is why LinkedIn’s web app, with hundreds of engineers, ships on Ember. The opinions scale; the decisions don’t multiply with headcount. 💡

The Architecture Pattern

The most common production shape for an Ember app: Ember owns the entire UI as a Single-Page Application (SPA), the backend is sliced into microservices, and Ember Data adapters glue them together over JSON:API.

1
2
3
4
5
6
7
8
9
┌───────────────────────────────────────────────┐
│              Ember.js SPA (browser)           │
│   Routes · Components · Services · Ember Data │
└──────────────┬────────────────────────────────┘
               │ JSON:API over HTTPS
      ┌────────┼────────┬────────────┬─────────┐
      ▼        ▼        ▼            ▼         ▼
   users     billing  catalog   notifications  search
   (Go)      (Java)   (Node)    (Python)       (Elastic)

A typical setup:

  • One Ember app — the monolithic frontend — owns routing, layout, design system, and user experience end to end. No micro-frontend fragmentation.
  • Backend split into focused services (auth, billing, catalog, search, notifications), each exposing JSON:API endpoints.
  • Ember Data adapters per model — sometimes per service — handle URL conventions, auth headers, and error normalization.
  • A thin gateway or Backend-for-Frontend (BFF) in front, if request aggregation or auth fan-out is needed.

An adapter pointing at a specific microservice looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/adapters/invoice.js — billing service
import JSONAPIAdapter from '@ember-data/adapter/json-api';
import { inject as service } from '@ember/service';

export default class InvoiceAdapter extends JSONAPIAdapter {
  @service session;

  host = 'https://billing.api.acme.com';
  namespace = 'v1';

  get headers() {
    return {
      Authorization: `Bearer ${this.session.token}`,
      Accept: 'application/vnd.api+json',
    };
  }
}

And a component that fetches and renders invoices is just:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app/components/invoice-list.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class InvoiceListComponent extends Component {
  @service store;
  @tracked invoices = [];

  constructor() {
    super(...arguments);
    this.load();
  }

  async load() {
    this.invoices = await this.store.findAll('invoice');
  }
}
1
2
3
4
5
6
{{! app/components/invoice-list.hbs }}
<ul>
  {{#each this.invoices as |invoice|}}
    <li>{{invoice.number}} — {{invoice.amount}}</li>
  {{/each}}
</ul>

That’s the whole component. No reducer, no slice, no thunk, no useEffect dance. The store handles caching, identity, and the network call.

Top 5 Ember Libraries Worth Knowing

A short tour of the addons that show up in almost every serious Ember production app. All of them are open source — click through and read the source, the Ember community’s code tends to be remarkably clean and well-tested. 📚

  1. ember-concurrency — Tasks instead of promises. Cancellation, debouncing, dropping, restartable behavior, all declarative. Once you’ve used it, raw promises feel primitive.
    Source: github.com/machty/ember-concurrency

  2. ember-data — Already mentioned, but worth listing. JSON:API client, identity map, relationships, dirty tracking. The reference implementation for how to talk to a REST/JSON:API backend from an SPA.
    Source: github.com/emberjs/data

  3. ember-power-select — A flexible, accessible, fully-themed select/typeahead component. Sounds boring; replaces about 800 lines of hand-rolled select logic in every app.
    Source: github.com/cibernox/ember-power-select

  4. ember-simple-auth — Authentication and session management. Pluggable authenticators (OAuth2 — the second version of Open Authorization, JWT, custom), session persistence, route mixins to gate access. The de facto auth layer.
    Source: github.com/mainmatter/ember-simple-auth

  5. ember-cli-mirage — A client-side API mock you wire into your dev server and acceptance tests. Define your backend’s shape in JavaScript, get a fake server with realistic latency and relationships. Invaluable when the backend microservice you depend on is still in flight.
    Source: github.com/miragejs/ember-cli-mirage

So Should You Pick Ember in 2020?

If you’re a solo developer building a side project, probably not — the conventions feel heavy when there’s no team to coordinate. If you’re a 3-person startup shipping a Minimum Viable Product (MVP) and you might pivot the whole stack next quarter, also probably not. (And yes, that’s Minimum Viable Product — not Most Valuable Player, and not Most Valuable Person. Tech acronyms collide constantly. 🏀)

But if you’re building an application that needs to be maintained by dozens of engineers across multiple squads for the next five years, and you’d rather argue about features than about folder structure — Ember is still one of the strongest bets you can make. The TypeScript story is getting better, Octane (the modern component model) has fixed most of the historical ergonomics complaints, and the upgrade treadmill is gentler than anything else in this space. 🚀

This entry was posted in javascript and tagged , , . Bookmark the permalink.

Comments are closed.