AlgoInsight

Free lesson · JavaScript Basic to Advance

Setting Up: Modules, Build Tools, Structure

Easy5 min read

The First Files

Before building any features, we set up the project structure. This Topic covers the scaffolding — the directory layout, the build configuration, the entry points. These decisions feel small but they shape everything that follows.

dashboard/
├── package.json
├── index.html
├── main.js
└── (modules we'll add as we build)

We start with three files. By the end of Module 15, we'll have about a dozen. Each addition is deliberate, and each one connects to material from earlier modules.

package.json: Where the Project Begins

Every Node-based project starts with package.json. We covered this in detail in Topic 11.6. Here's the minimal version for our dashboard:

{
  "name": "dashboard",
  "version": "0.1.0",
  "description": "Async data dashboard — capstone project",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "^5.0.0"
  }
}

A few decisions here, each connected to course material:

"type": "module" — Topic 11.2 covered this. It tells Node to treat .js files as ES Modules. Without this, we'd have to use the .mjs extension or stick with CommonJS. For modern code in 2026, ES Modules are the right default.

Vite as the build tool. We're using Vite (a modern bundler) for development and production builds. The strategy decision here: avoid raw <script> tags or hand-managed loading order. Modern projects use a bundler. Vite is one of several good choices (esbuild, Webpack, Parcel, Rspack). For a small project, Vite has the lowest friction.

We installed Vite as a devDependency because it's needed for development and building, but not for the runtime of the deployed application. Topic 11.6 covered the dependencies vs devDependencies distinction.

Interactive animation: Two jobs, both demystified: vite serves native ES modules in development, and vite build bundles for production.

index.html: The Entry Point

The HTML file is intentionally minimal:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dashboard</title>
</head>
<body>
  <div id="app">
    <header>
      <h1>Product Dashboard</h1>
      <input type="text" id="search" placeholder="Search products...">
      <select id="sort">
        <option value="name">Sort by name</option>
        <option value="price">Sort by price</option>
        <option value="rating">Sort by rating</option>
      </select>
    </header>
    <main id="list"></main>
    <div id="status"></div>
  </div>
  <script type="module" src="/main.js"></script>
</body>
</html>

A few things to notice:

<script type="module"> — covered in Topic 11.1. Telling the browser to load main.js as an ES Module. Without this attribute, the browser would treat it as a script with global scope, and import statements wouldn't work.

Skeleton structure. A few semantic elements (header, main) and a few id-anchored slots for our JavaScript to fill in. We're not generating the entire DOM from JavaScript — we use the HTML for static structure and JavaScript for the dynamic parts.

The placeholders. #app, #search, #sort, #list, #status are the hooks we'll target from JavaScript. Topic 12.2 covered DOM selection with getElementById and querySelector — we'll use both throughout.

No CSS. We'll add styles inline or in a separate file as we go. The project's clarity comes from the JavaScript, not from styling.

main.js: The Bootstrap

The entry point connects all the pieces:

// main.js — the bootstrap
import { Store } from "./state/store.js";
import { Api } from "./data/api.js";
import { createCache } from "./data/cache.js";
import { renderList } from "./ui/list.js";
import { setupFilters } from "./ui/filters.js";
import { showError } from "./ui/errors.js";
import { debounce } from "./lib/utils.js";

// (For now, we mock these imports — we'll build the real ones in later Topics)
// This is just to show the structure.

console.log("Dashboard bootstrapping...");

// 1. Create the cache (closure-based, from Module 6)
// const cache = createCache({ maxSize: 50, ttl: 60_000 });

// 2. Create the API wrapper (uses the cache)
// const api = new Api({ cache, baseUrl: "/api" });

// 3. Create the state store (class from Module 8)
// const store = new Store({
//   query: "",
//   sort: "name",
//   items: [],
//   loading: false,
//   error: null
// });

// 4. Subscribe to state changes — re-render when state updates
// store.subscribe(state => {
//   renderList(document.getElementById("list"), state);
//   if (state.error) showError(document.getElementById("status"), state.error);
// });

// 5. Wire up UI events (delegation, from Module 12)
// setupFilters({
//   onSearch: debounce(query => store.update({ query }), 200),
//   onSort: sort => store.update({ sort })
// });

// 6. Initial fetch
// store.update({ loading: true });
// api.fetch({ query: "", sort: "name" })
//   .then(items => store.update({ items, loading: false }))
//   .catch(error => store.update({ error, loading: false }));

console.log("Bootstrap complete — see next Topics for implementation");

The comments in this file are a roadmap for the next three Topics. Each commented-out section is a piece we'll build.

A few patterns worth highlighting at this level:

Dependency direction. main.js imports from the data, state, UI, and lib modules. None of those modules import from main.js. This keeps the dependency graph clean — main is the composition root, and other modules are independent of how main wires them together.

Composition over inheritance. Topic 8.6 covered this. The Store doesn't extend anything. The Api doesn't extend anything. They're composed together in main.js. This is the architecture senior reviewers expect.

State subscription. The Store will use a subscriber pattern — registered callbacks fire whenever state changes. This is the pattern Redux, Zustand, and most state libraries use. We're implementing it from scratch.

Module Boundaries

A useful exercise as we build: for each module, ask "what's its job, and what does it expose?"

Module Job Exposes
data/api.js Fetch from the API, handle HTTP errors, support cancellation Api class with fetch(query) method
data/cache.js Store and retrieve responses by query Factory function createCache(options)
state/store.js Hold app state, notify subscribers on change Store class with update() and subscribe()
ui/list.js Render the list of items based on state renderList(container, state) function
ui/filters.js Render filter controls, wire up event handlers setupFilters(handlers) function
ui/errors.js Render error states with retry button showError(container, error) function
lib/utils.js Generic utilities used across modules debounce, classnames (named exports)

Each module has a clear contract. You can swap one for another without changing the others, as long as the new one exposes the same interface. This is the modularity principle from Topic 11.1.

Why This Structure

We could have built this as one big main.js file with everything inline. It would work for a project this small. We're using multiple modules for two reasons:

1. To demonstrate module patterns from Module 11. A capstone that uses one file wouldn't exercise the module system. Splitting forces us to think about imports, exports, and boundaries.

2. To make the project extensible. Adding features later — pagination, sorting, more filters — is much easier when the codebase is already partitioned by concern. A monolithic main.js would need refactoring before you could add anything substantial.

In real codebases, the trade-off is real. Tiny scripts benefit from being inline. Anything that will grow benefits from being modular from the start. For interview prep, the modular version is what you should be able to write fluently.

Common Pitfalls

Warning: The setup traps.

Forgetting "type": "module" in package.json. Without it, Node treats .js files as CommonJS, and import statements throw. Many "import syntax error" bugs come from this.

Mixing tools you don't need. A small capstone project doesn't need Webpack with custom loaders, eight Babel plugins, and a complex test setup. Vite gives you ES Modules, hot reload, and a production build in one tool. Start minimal.

Skipping the file extension in imports. Native ES Modules require the .js extension. Bundlers often allow omitting it, which can cause confusion when moving between environments. Use the extension explicitly to avoid surprises.

What's Next

The setup is done. The next Topic (15.3) builds the data layer — the API wrapper and the cache. The cache demonstrates closures concretely. The API wrapper demonstrates Promise patterns and error handling. Both connect to course material from Modules 6 and 9.

After that, Topic 15.4 builds the UI layer with DOM and events. Topic 15.5 integrates everything with the state store. Topic 15.6 turns the whole thing into a comprehensive interview question bank.

Try It

Exercise: Set up the project skeleton on your machine:

  1. Create a directory, run npm init -y
  2. Add "type": "module" to package.json
  3. Install Vite as a dev dependency: npm install -D vite
  4. Create the directory structure from the table above (empty files are fine)
  5. Create the basic index.html from this Topic
  6. Run npx vite and visit the URL

You should see the page with the search input, sort dropdown, and empty list. Nothing functional yet — but the scaffolding is in place. The next Topic fills in the data layer.

Back to JavaScript Basic to Advance