Observable Framework 1.0.0 GitHub

Getting started

Welcome! This tutorial will guide your first steps with Observable Framework by way of a hands-on exercise creating a dashboard of local weather. 🌦️

Observable Framework — or “Framework” for short — is an open-source system for building data apps, dashboards, and reports that combines the power of JavaScript on the front-end for interactive graphics with any language you want on the back-end for data preparation and analysis.

Framework is three things in one:

We’ll touch on each of these parts in this tutorial. It’ll go something like this:

An overview of developing with Framework.

First you’ll setup your local development environment by creating a project. Next you’ll develop: an iterative process where you save changes to source files in your editor while previewing the result in your browser. When you’re ready to share, it’s time to publish: you can either build a static site for self-hosting or deploy directly to Observable. Lastly, you can invite people to view your project!

These are just first steps. You can continue to develop projects after publishing, and republish as needed. You can also setup continuous deployment to publish your site automatically on commit or on schedule. We’ll cover these next steps briefly below.

1. Create

Framework includes a helper script (observable create) for creating new projects. After a few quick prompts — where to create the project, your preferred package manager, etc. — it will stamp out a fresh project from a template.

Framework is a Node.js application published to npm. You must have Node.js 20.6 or later installed before you can install Framework. Framework is a command-line interface (CLI) and runs in the terminal.

If you run into difficulty following this tutorial, we’re happy to help! Please visit the Observable forum or our GitHub discussions.

To create a new project with npm, run:

npm init @observablehq

If you prefer Yarn, run:

yarn create @observablehq

You can run the above command anywhere, but you may want to cd to your ~/Development directory first (or wherever you do local development).

The first prompt asks where to create your new project. Enter ./hello-framework to create a directory named hello-framework within the current directory. Or just hit Enter, as this is conveniently the default. (The ./ is implied, so ./hello-framework is equivalent to hello-framework. You can create a project in a different directory by entering a path that starts with ../ or ~/ or /.)

   observable create 

  Where to create your project?
  ./hello-framework 

Next you’ll enter the project’s title. A project’s title appears in the sidebar as well as on all pages. You can hit Enter here to accept the default title derived from the directory name.

   observable create 

  Where to create your project?
  ./hello-framework

  What to title your project?
  Hello Framework

Next, decide whether you want sample files in your new project. These files demonstrate common techniques and are handy for learning — you can edit the code and see what happens. But if you’d prefer a more minimal starter project with less to delete later, you can omit them. They’re not needed for this tutorial.

   observable create 

  Where to create your project?
  ./hello-framework

  What to title your project?
  Hello Framework

  Include sample files to help you get started?
   Yes, include sample files (recommended)
  ○ No, create an empty project

If you use npm or Yarn as your preferred package manager, declare your allegiance now. The package manager you used to launch observable create will be selected by default, so you can just hit Enter again to continue. If you prefer a different package manager (say pnpm), choose No; you can always install dependencies after the project is created.

   observable create 

  Where to create your project?
  ./hello-framework

  What to title your project?
  Hello Framework

  Include sample files to help you get started?
  Yes, include sample files

  Install dependencies?
  ○ Yes, via npm
   Yes, via yarn (recommended)
  ○ No

If you’ll continue developing your project after you finish this tutorial and want source control, answer Yes to initialize a git repository. Or say No — you can always do it later by running git init.

   observable create 

  Where to create your project?
  ./hello-framework

  What to title your project?
  Hello Framework

  Include sample files to help you get started?
  Yes, include sample files

  Install dependencies?
  Yes, via yarn

  Initialize a git repository?
   Yes / ○ No

And that’s it! After some downloading, copying, and installing, your new project is ready to go. 🎉

   observable create 

  Where to create your project?
  ./hello-framework

  What to title your project?
  Hello Framework

  Include sample files to help you get started?
  Yes, include sample files

  Install dependencies?
  Yes, via yarn

  Initialize a git repository?
  Yes

  Installed! 🎉

  Next steps… ──────────╮
                        
  cd ./hello-framework  
  yarn dev              
                        
├────────────────────────╯

  Problems? https://observablehq.com/framework/getting-started

2. Develop

Next, cd into your new project folder.

cd hello-framework

Framework’s local development server lets you preview your site in the browser as you make rapid changes. The preview server generates pages on-the-fly: as you edit files in your editor, changes are instantly streamed to your browser.

You can work offline with the preview server, but you must be connected to the internet to import libraries from npm. In the future, we intend to support self-hosting imported libraries; please upvote #20 and #360 if you are interested in this feature.

To start the preview server using npm:

npm run dev

Or with Yarn:

yarn dev

You should see something like this:

Observable Framework v1.0.0
↳ http://127.0.0.1:3000/

If port 3000 is in use, the preview server will choose the next available port, so your actual port may vary. To specify port 4321 (and similarly for any other port), use --port 4321.

For security, the preview server is by default only accessible on your local machine using the loopback address 127.0.0.1. To allow remote connections, use --host 0.0.0.0.

Now visit http://127.0.0.1:3000 in your browser, which should look like:

The default home page (docs/index.md) after creating a new project.

Test live preview

Live preview means that as you save changes, your in-browser preview updates instantly. Live preview applies to Markdown pages, imported JavaScript modules (so-called hot module replacement), data loaders, and file attachments. This feature is implemented by the preview server watching files and pushing changes to the browser over a socket.

To experience live preview, open docs/index.md in your preferred text editor — below we show Visual Studio Code — and position your browser window so that you can see your editor and browser side-by-side. If you then replace the text “Hello, Observable Framework” with “Hi, Mom!” and save, you should see:

No seriously — hi, Mom! Thanks for supporting me all these years.
If you don’t see an update after saving, try reloading. The preview socket may disconnect if you’re idle. Please upvote #50 if you run into this issue.

Create a new page

Now let’s add a page for our weather dashboard. Create a new file docs/weather.md and paste in the following snippet:

# Weather report

```js
1 + 2
```

To see the new page in the sidebar, you must restart the preview server. In the terminal, use Control-C (⌃C) to kill the preview server. Then use up arrow (↑) to re-run the command to start the preview server (npm run dev or yarn dev). Lastly, reload your browser. A bit of rigamarole, but you won’t have to do it often… 😓 Upvote #645 and #646 if you’d like this to be better.

If you click on the Weather report link in the sidebar, it’ll take you to http://127.0.0.1:3000/weather, where you should see:

The humble beginnings of a local weather dashboard.
The sidebar is hidden by default in narrow windows. If you don’t see the sidebar, you can show it by making the window wider, or using Command-B (⌘B) or Option-B (⌥B) on Firefox and non-macOS, or clicking the right-pointing arrow ↦ on the left edge of the window.

As evidenced by the code 1 + 2 rendered as 3, JavaScript fenced code blocks (```js) are live: the code runs in the browser. Try replacing 2 with Math.random(), and the code will re-run automatically on save. In a bit, we’ll write code to render a chart. We can also use code to debug as we develop, say to inspect data.

Data loader

Next, let’s load some data. The National Weather Service (NWS) provides an excellent and free API for local weather data within the United States. We’ll use the /points/{latitude},{longitude} endpoint to get metadata for the closest grid point to the given location, and then fetch the corresponding hourly forecast.

Create a new file docs/data/forecast.json.js and paste in the following snippet:

const longitude = ;
const latitude = ;

async function json(url) {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`fetch failed: ${response.status}`);
  return await response.json();
}

const station = await json(`https://api.weather.gov/points/${latitude},${longitude}`);
const forecast = await json(station.properties.forecastHourly);

process.stdout.write(JSON.stringify(forecast));

To personalize this code snippet to your current location, edit the longitude and latitude values above, or click the Locate me button above.

NWS does not provide forecasts for points outside the United States, so if you specify such a location the API will return an error and the data loader will fail.
If you would rather write your data loader in Python, R, or some other language, take a peek at the next steps below before continuing.

Your data loader should look like this:

A JavaScript data loader for fetching a local forecast from weather.gov.

If you like, you can run your data loader manually in the terminal:

node docs/data/forecast.json.js

If this barfs a bunch of JSON in the terminal, it’s working as intended. 😅 Normally you don’t run data loaders by hand — Framework runs them automatically, as needed — but data loaders are “just” programs so you can run them manually if you want. Conversely, any executable or shell script that runs on your machine and outputs something to stdout can be a data loader!

File attachments

Framework uses file-based routing not just for pages but for data loaders as well: the data loader forecast.json.js serves the file forecast.json. To load this file from docs/weather.md we use the relative path ./data/forecast.json. In effect, data loaders are simply a naming convention for generating “static” files — a big advantage of which is that you can edit a data loader and the changes immediately propagate to the live preview without needing a reload.

To load a file in JavaScript, use the built-in FileAttachment. In weather.md, replace the contents of the JavaScript code block (the parts inside the triple backticks ```) with the following code:

const forecast = FileAttachment("./data/forecast.json").json();
FileAttachment is a special function that can only be passed a static string literal as an argument. This restriction enables static analysis, allowing Framework to determine which data loaders to run on build and improving security by only including referenced files in the published site.

You can now reference the variable forecast from other code. For example, you can add another code block that displays the forecast data.

display(forecast);

This looks like:

Using FileAttachment to load data.

The built-in display function displays the specified value, a bit like console.log in the browser’s console. As you may have noticed above with 1 + 2, display is called implicitly when a code block contains an expression.

For convenience, here’s a copy of the data so you can explore it here:

This is a GeoJSON Feature object of a Polygon geometry representing the grid square. The properties object within contains the hourly forecast data. You can display it on a map with Leaflet, if you like.

This grid point covers the south end of the Golden Gate Bridge.

Plots

Now let’s add a chart using Observable Plot. Framework includes a variety of recommended libraries by default, including Plot, and you can always import more from npm. Replace the display(forecast) code block with the following code:

Plot.plot({
  title: "Hourly temperature forecast",
  x: {type: "utc", ticks: "day", label: null},
  y: {grid: true, inset: 10, label: "Degrees (F)"},
  marks: [
    Plot.lineY(forecast.properties.periods, {
      x: "startTime",
      y: "temperature",
      z: null, // varying color, not series
      stroke: "temperature",
      curve: "step-after"
    })
  ]
})
Because this is JSON data, startTime is a string rather than a Date. Setting the type of the x scale to utc tells Plot to interpret these values as temporal rather than ordinal.

You should now see:

Using Plot to make a chart.
Try editing forecast.json.js to change the longitude and latitude to a different location! After you save, Framework will run the data loader again and push the new data to the client to update the chart. For example, to see the current forecast at the White House:
const longitude = -77.04;
const latitude = 38.90;

As before, the code block contains an expression (a call to Plot.plot) and hence display is called implicitly. And since this expression evaluates to a DOM element (a <figure> containing an <svg>), display inserts the element directly into the page. We didn’t have to touch the DOM API!

Components

As pages grow, complex inline JavaScript may become unwieldy and repetitive. Tidy code by moving it into functions. In Framework, a function that returns a DOM element is called a component.

To turn the chart above into a component, wrap it in a function and promote the data to a required argument. Accept any named options (such as width) as an optional second argument with destructuring.

function temperaturePlot(data, {width} = {}) {
  return Plot.plot({
    title: "Hourly temperature forecast",
    width,
    x: {type: "utc", ticks: "day", label: null},
    y: {grid: true, inset: 10, label: "Degrees (F)"},
    marks: [
      Plot.lineY(data.properties.periods, {
        x: "startTime",
        y: "temperature",
        z: null, // varying color, not series
        stroke: "temperature",
        curve: "step-after"
      })
    ]
  });
}

Now you can call temperaturePlot to display the forecast anywhere on the page:

temperaturePlot(forecast)
JavaScript can be extracted into standalone modules (.js files) that you can import into Markdown. This lets you share code across pages, write unit tests for components, and more.

Layout

Let’s put some finishing touches on and wrap up this tutorial.

While this nascent dashboard only has a single chart on it, most dashboards will have many charts, tables, values, and other elements. To assist layout, Framework includes simple grid and card CSS classes with 1, 2, 3, or 4 columns. (You can write more elaborate custom styles if needed, or load your preferred CSS framework.)

For example, here’s a two-column grid with three cards:

<div class="grid grid-cols-2">
  <div class="card grid-colspan-2">one–two</div>
  <div class="card">three</div>
  <div class="card">four</div>
</div>
Framework’s grid is responsive: on narrow windows, the two-column grid will automatically collapse to a one-column grid. Cells in a grid have the same height by default (using grid-auto-rows), so consider separate <div class="grid"> containers if you want to vary row height.

When placing charts in a grid, you typically want to render responsively based on the width (and sometimes height) of the containing cell. Framework’s resize helper takes a render function returning a DOM element and re-renders whenever the container resizes. It looks like this:

<div class="grid grid-cols-1">
  <div class="card">${resize((width) => temperaturePlot(forecast, {width}))}</div>
</div>

Lastly, let’s apply the dashboard theme and disable the table of contents (toc) using YAML front matter. The dashboard theme allows the main column to span the full width of the window; without it, the main column width is limited to 1152px as appropriate for documentation or a report.

---
theme: dashboard
toc: false
---
Adopting a grid layout and the dashboard theme.

Ta-da! 🎉 Perhaps not the most exciting dashboard yet, but it has potential! Try exploring other data in the NWS forecast and adding more charts. For example, you could visualize precipitation probability.

3. Publish

When you’re ready to share your project — whether privately with specific people you want to invite, or publicly with the world — you can quickly deploy it to Observable using the deploy command:

npm run deploy

Or with Yarn:

yarn deploy
If you don’t have an Observable account yet, the first time you deploy you’ll be prompted to create one. Observable is free for individuals and small teams, and we offer paid tiers for larger teams.

If this is your first time deploying to Observable, you’ll be prompted to create a new project. This determines where you project lives on Observable.

Your deploy configuration is saved to docs/.observablehq/deploy.json. When collaborating on a project, you should commit this file to git so your collaborators don’t have to separately configure deploy.
   observable deploy 

  To configure deploy, we need to ask you a few questions.

  Deploying to the ACME Inc. (@acme) workspace.

  No projects found. Do you want to create a new project?
   Yes, continue / ○ No, cancel

If you have multiple workspaces on Observable, you’ll be prompted to chose a workspace before creating a project. And if you’ve previously deployed projects to your chosen workspace, you can chose to deploy to an existing project, overwriting its contents.

When creating a new project, you need to specify a slug which — together with your workspace username — determines the URL of your project. The slug is a short identifier consisting of lowercase letters, numbers, and hyphens. By default, Framework will suggest a slug based on your project’s title.

   observable deploy 

  To configure deploy, we need to ask you a few questions.

  Which Observable workspace do you want to use?
│  ACME Inc. (@acme)

  Which project do you want to use?
│  Create a new project

  What slug do you want to use?
  hello-framework

You can change the slug later, and Observable will automatically redirect to the latest URL.

Lastly, you can enter an optional deploy message. Deploy messages are shown on Observable and help you keep track of deploy history. For now, you can just leave this blank by hitting Enter.

   observable deploy 

  To configure deploy, we need to ask you a few questions.

  Which Observable workspace do you want to use?
│  ACME Inc. (@acme)

  Which project do you want to use?
│  Create a new project

  What slug do you want to use?
│  hello-framework

  What changed in this deploy?
  Enter a deploy message (optional)

Deploy messages can be set using deploy --message. This is especially useful for continuous deployment from a git repository: the message can include the SHA, author, and message of the latest commit.

When deploy completes, Framework will show your project’s URL on observablehq.cloud. From there you can invite people to your private workspace to see your project, or make your project public so anyone can see it.

Self hosting

Of course, you don’t have to deploy to Observable — Framework projects are simply static sites, so you can host them anywhere!

To build your static site with npm, run:

npm run build

Or with Yarn:

yarn build

The build command generates the dist directory; you can then copy this directory to your static site server or preferred hosting service. To preview your built site locally, you can use a local static HTTP server such as http-server:

npx http-server dist

Next steps

Here are a few more tips.

Write a data loader in Python, R, or other language

We coded exclusively in JavaScript for this tutorial, but you can write data loaders in any language — not just JavaScript. Here’s a forecast.json.py you could use in place of the JavaScript data loader above:

import json
import requests
import sys

longitude = -122.47
latitude = 37.80

station = requests.get(f"https://api.weather.gov/points/{latitude},{longitude}").json()
forecast = requests.get(station["properties"]["forecastHourly"]).json()

json.dump(forecast, sys.stdout)

To write the data loader in R, name it forecast.json.R. Or as shell script, forecast.json.sh. You get the idea. See Data loaders: Routing for more. The beauty of this approach is that you can leverage the strengths (and libraries) of multiple languages, and still get instant updates in the browser as you develop.

Deploying via GitHub Actions

You can schedule builds and deploy your project automatically on commit, or on a schedule. See this documentation site’s deploy.yml for an example.

Ask for help, or share your feedback

Please reach out if you have questions or thoughts! You can post on the Observable forum, start a GitHub discussion, or file a GitHub issue. And if you like Framework, please give us a star ⭐️ on GitHub — we appreciate your support. 🙏