The Doe family project structure

How the Doe family organizes their dbt project — directories, dbt_project.yml, and the sources → staging → marts layout that aligns with how nexus is consumed.

Learning Objectives

By the end of this lesson, you will be able to:

  • Identify the standard directories in a dbt project
  • Read and edit dbt_project.yml
  • Understand the sources → staging → marts model layout
  • Recognize what packages.yml is for (preview of Module 3)

The project skeleton

After dbt init (or initializing in dbt Cloud), your project looks roughly like this:

doe_family/
├── analyses/
├── macros/
├── models/
├── seeds/                ← family_members.csv lives here (lesson 2.1)
├── snapshots/
├── tests/
├── dbt_project.yml
├── packages.yml          ← you'll add this in Module 3
└── README.md
Directory What lives there
models/ Your SQL transformations. Each .sql file becomes a table or view.
seeds/ CSV files. dbt seed loads them as tables (covered in 2.1).
macros/ Reusable Jinja functions (2.6).
tests/ Custom data tests beyond the built-in ones.
snapshots/ SCD2-style history capture for source tables (not used in this course).
analyses/ Ad-hoc SQL files dbt compiles but doesn't run. Rarely used.

The two you'll touch most are models/ and seeds/.


dbt_project.yml

The control file for the whole project. The Doe family's looks like this:

name: doe_family
version: 1.0.0
config-version: 2

profile: doe_family    # references the profile in ~/.dbt/profiles.yml
                       # (or, in dbt Cloud, the profile bound to the project)

model-paths: ["models"]
seed-paths: ["seeds"]
test-paths: ["tests"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]
analysis-paths: ["analyses"]

clean-targets:
  - "target"
  - "dbt_packages"

models:
  doe_family:
    sources:
      +materialized: view
    staging:
      +materialized: view
    marts:
      +materialized: table

vars:
  # Populated in Module 3 when we install dbt-nexus

Two things to notice:

  • The models: block sets defaults by directory. Anything under models/sources/ is a view; anything under models/marts/ is a table. Individual models can override with {{ config(...) }} — see 2.4 Materializations.
  • The vars: block is where dbt-nexus configuration will land in Module 3.

The sources → staging → marts layout

The Doe family lays out models/ in three layers:

models/
├── sources/      # one folder per source system; raw → standardized
│   ├── gmail/
│   ├── google_calendar/
│   └── notion/
├── staging/      # nexus-shaped intermediate models (events, identifiers, traits)
│   ├── gmail/
│   ├── google_calendar/
│   └── notion/
└── marts/        # the family-facing outputs
    ├── family_contacts.sql
    └── christmas_card_list.sql

Why this layering matters:

Layer Purpose Materialization
sources/ Light cleaning of raw warehouse data (rename, cast, dedupe) view
staging/ Shape source data into nexus's required outputs (events, etc.) view / table
marts/ The tables the family actually queries table

You don't have to use this exact layout — but it mirrors how nexus is designed to be consumed, which makes the transition into Module 3 seamless.

The seed you loaded in 2.1 lives in seeds/, not models/, but downstream marts/ can ref() it the same as any model.


packages.yml (preview)

In Module 3 you'll create packages.yml to install dbt-nexus. It isn't on the dbt Hub, so it installs directly from Git:

packages:
  - package: dbt-labs/dbt_utils
    version: 1.3.0
  - git: https://github.com/slide-rule-tech/dbt-nexus
    revision: v0.9.1

dbt deps installs declared packages into dbt_packages/ (which your .gitignore already excludes). Packages can ship models, macros, and tests; dbt-nexus ships all three. See 2.6 Macros and packages for the deeper take.


Hands-On Exercise

  1. Create the directory structure above:

    mkdir -p models/sources models/staging models/marts
    
  2. Open dbt_project.yml and add the models: block with per-directory materialization defaults shown above.

  3. Create models/marts/family_size.sql:

    select
      count(*) as n_members,
      countif(role = 'parent') as n_parents,
      countif(role = 'child')  as n_children
    from {{ ref('family_members') }}
    
  4. Run dbt build -s family_size and check the result in BigQuery. Confirm it's a table (your marts/ default), not a view.

  5. Commit the changes on a feature branch, push it to GitHub, and open a PR (1.5 muscle memory).


Summary

Concept Key takeaway
Project skeleton models/, seeds/, macros/, tests/, dbt_project.yml
dbt_project.yml Project config + per-directory model defaults
Layered models sources → staging → marts keeps raw, shaped, and final cleanly separated
Why this layout Mirrors how nexus is consumed; sets you up for Module 3
packages.yml Where you'll declare dbt-nexus in Module 3

Next Lesson

You've got dbt set up, the project structured the way nexus expects, and every core concept covered. Last stop in Module 2: 2.8 Examples and practicum — three worked examples and a consolidated practicum to lock everything in before we dive into nexus.