JOUR 73361

Coding the News

Learn how America's top news organizations escape rigid publishing systems to design beautiful data-driven stories on deadline.

Ben Welsh, Adjunct Assistant Professor

Spring 2026

Mondays 6–9 p.m.

Lab 436

Week 7

Practice, Practice, Practice

A refresher exercise that revisits JavaScript fundamentals

March 16, 2026

Today's class is a deliberate step back.

The findings from our student survey were clear: You want to spend more time on JavaScript fundamentals, the basics we rushed past quickly in earlier weeks. And then, once you feel more comfortable, revisit how to load data and craft interactive pages.

So that's the plan.

Starting from a blank page, you'll build a new interactive application, step by step, with a focus on understanding the JavaScript concepts at work.

Once you're finished, you'll be ready to start building real data projects in our class's next phase.

The data you're using is film and television shoot permits published by New York City's Mayor's Office of Media and Entertainment. Every time a production company wants to prevent parking, block a sidewalk or take over a piece of city property, they file a permit. After a few months go by, those notices end up here.

Film Permits
https://data.cityofnewyork.us/City-Government/Film-Permits/tg4x-b46p

Before you start using it, get your tools out. Open your browser and navigate to our template repository at github.com/palewire/cuny-jour-static-site-template.

cuny-jour-static-site-template
https://github.com/palewire/cuny-jour-static-site-template

Click the green "Use this template" button and create a new repository named film-permits. Make sure "Public" is selected, then click "Create repository."

Clone it onto your computer, open it in Visual Studio Code, and run npm install in the terminal. These steps should feel familiar by now.

Start the development server with npm run dev and open your browser to localhost:5173.

Part 1: Variables

In Week 5, you wrote a Node.js script to download restaurant inspection data You're going to do the same thing here with film permits. This time you'll take it slow and I'll talk through every line of code, so you understand exactly what's happening, and learn some JavaScript along the way.

Create a new file in the root of your project called fetch-data.js. Start with this:

const url = "https://data.cityofnewyork.us/resource/tg4x-b46p.json?$where=eventtype='Shooting Permit'&$order=startdatetime,eventid DESC&$limit=25";
console.log(url);

It starts with a single variable declaration. A variable is a named container for a value. In this case, the variable is called url and it holds the address of the API endpoint where your data lives.

The console.log function is a built-in way to print something out. It's a crucial tool for debugging your code.

Now open a new terminal run it.

node fetch-data.js

You should see the full URL printed in your terminal. Nothing fancy yet.

https://data.cityofnewyork.us/resource/tg4x-b46p.json?$where=eventtype=%27Shooting%20Permit%27&$order=startdatetime,eventid%20DESC&$limit=25

That const keyword, short for constant, is how you declare a variable in JavaScript when you know it won't change. If it might change later, you use let instead, which is a term taken from mathematics and older programming languages like BASIC.

For example:

// will never be reassigned
const title = 'Recent Film Permits';

// will be reassigned later: count = count + 1;
let count = 0;

You've been writing let all semester for Svelte's reactive variables. That's correct there because $state() variables get reassigned as the user interacts with the page. But in a standalone script like this one, most of the values you create can be constants.

The url variable holds the address of the API endpoint. The $where, $order and $limit parts at the end are query parameters in the Socrata Open Data API format, which is what NYC Open Data uses. They tell the API to give us only shooting permits, sorted newest first, limited to 25 records. Note that these $ signs are part of the Socrata query language. They have nothing to do with Svelte's syntax, which uses $ for reactive variables. It's just a coincidence that they look the same.

Part 2: Fetching data

Now let's actually talk to the server. Update fetch-data.js to make a web request.

const url = "https://data.cityofnewyork.us/resource/tg4x-b46p.json?$where=eventtype=%27Shooting%20Permit%27&$order=startdatetime,eventid%20DESC&$limit=25";
const response = await fetch(url);
const permits = await response.json();
console.log(permits);

The fetch() function is JavaScript's built-in way of making a web request. You pass it the URL and it starts talking to the server.

The await keyword tells JavaScript to pause and wait for the server to respond before continuing. Without await, your code would try to use the response variable before the data had arrived, and you'd get nothing.

The .json() call on the second await line takes the raw server response, which arrives as a stream of text, and parses it into a JavaScript object. This also takes a moment, which is why it also needs await.

The updated console.log line prints the final result, a list of permits.

To see them, run the script again:

node fetch-data.js

You should see see a list of 25 permit objects printed in your terminal. Here's what the top of that looks like.

$ node fetch-data.js
[
  {
    eventid: '904687',
    eventtype: 'Shooting Permit',
    enddatetime: '2025-12-11T20:00:00.000',
    enteredon: '2025-12-08T15:26:10.000',
    eventagency: "Mayor's Office of Media & Entertainment",
    parkingheld: "ST ANN'S AVENUE between EAST  143 STREET and EAST  144 STREET",
    borough: 'Bronx',
    communityboard_s: '1,',
    policeprecinct_s: '40,',
    category: 'Television',
    subcategoryname: 'Cable-episodic',
    country: 'United States of America',
    zipcode_s: '10454,'
  },
  {
    eventid: '894297',
    eventtype: 'Shooting Permit',
    enddatetime: '2025-10-11T02:30:00.000',
    enteredon: '2025-10-08T06:14:00.000',
    eventagency: "Mayor's Office of Media & Entertainment",
    parkingheld: 'WATER STREET between PECK SLIP and DOVER STREET,  PECK SLIP between WATER STREET and SOUTH STREET,  PECK SLIP between WATER STREET and FRONT STREET,  FRONT STREET between DOVER STREET and PECK SLIP,  BROOME STREET between BOWERY and ELIZABETH STREET,  DOVER STREET between PEARL STREET and FRONT STREET,  PELL STREET between BOWERY and MOTT STREET,  MOSCO STREET between MOTT STREET and MULBERRY STREET,  MOTT STREET between CANAL STREET and WORTH STREET,  CANAL STREET between MOTT STREET and ELIZABETH STREET,  CHATHAM SQUARE between DOYERS STREET and MOTT STREET,  EAST BROADWAY between MARKET STREET and PIKE STREET,  DOYER STREET between PELL STREET and BOWERY,  PEARL STREET between PECK SLIP and BEEKMAN STREET,  BOWERY between CANAL STREET and KENMARE STREET',
    borough: 'Manhattan',
    communityboard_s: '1, 2, 3,',
    policeprecinct_s: '1, 5,',
    category: 'Television',
    subcategoryname: 'Episodic series',
    country: 'United States of America',
    zipcode_s: '10002, 10012, 10013, 10038,'
  },
  ...

Part 3: Lists

You're looking at a JavaScript array, which is the fancy computer programming term for an ordered list of items.

In this case, each item in the list is an object, which is the fancy computer programming term for a thing that has named fields that store values, much like how a row in a spreadsheet is defined by its column names.

So, you should see 25 permit objects printed in your terminal, each with an identical set of fields like borough, parkingheld, subcategoryname and so on. Those names are from the header row of the original spreadsheet that the city published, and they are how you access the data you need in each object.

You access items in your code based on their position within the list using square brackets. Arrays in JavaScript, and most other programming languages, are zero-indexed, meaning the first item is at position 0, not position 1.

To give it a try, add these lines right after your console.log(permits):

console.log(permits[0]);
console.log(permits[0].subcategoryname);
console.log(permits[0].borough);

Run the script again and check the output.

node fetch-data.js

The first line prints the entire first permit object. The second and third print individual fields.

{
  eventid: '904687',
  eventtype: 'Shooting Permit',
  enddatetime: '2025-12-11T20:00:00.000',
  enteredon: '2025-12-08T15:26:10.000',
  eventagency: "Mayor's Office of Media & Entertainment",
  parkingheld: "ST ANN'S AVENUE between EAST  143 STREET and EAST  144 STREET",
  borough: 'Bronx',
  communityboard_s: '1,',
  policeprecinct_s: '40,',
  category: 'Television',
  subcategoryname: 'Cable-episodic',
  country: 'United States of America',
  zipcode_s: '10454,'
}
Cable-episodic
Bronx

This bracket-and-dot notation is how you dig into a JavaScript object. The bracket [0] gets you the item at position zero. The .subcategoryname gets you the field named subcategoryname from that item. Every object in the array has the same fields, so permits[1].subcategoryname would give you the subcategory of the second permit, and so on.

Part 4: Iteration

Accessing one item at a time is useful for exploration, but in practice you almost always want to do something with every item in an array. That's what programmers call iteration.

JavaScript arrays have a set of built-in methods for this. You'll cover two of them.

The first is .map(). It takes a function and applies it to every item in the array, returning a new array of the results. Think of it as "transform every item."

Add this after your individual console.log lines:

const subcategories = permits.map(permit => permit.subcategoryname);
console.log(subcategories);

Run the script and check the output.

node fetch-data.js

You should see an array of 25 subcategory names — just that field, extracted from every record.

[
  'Cable-episodic',  'Episodic series',
  'Cable-episodic',  'Cable-episodic',
  'Episodic series', 'Episodic series',
  'Not Applicable',  'Feature',
  'Feature',         'Commercial',
  'Episodic series', 'Not Applicable',
  'Feature',         'Feature',
  'Feature',         'Feature',
  'Not Applicable',  'Feature',
  'Feature',         'Commercial',
  'Feature',         'Cable-episodic',
  'Feature',         'Feature',
  'Feature'
]

The permit => permit.subcategoryname part is an arrow function. It's a compact way to write a function that takes one argument and returns a value from it.

The second method is .filter(). It takes a function that returns true or false, and keeps only the items where the function returns true. Think of it as "keep only the items that match."

Add this line:

const manhattan = permits.filter(permit => permit.borough === 'Manhattan');
console.log(manhattan.length);

The === operator is JavaScript's strict equality check. It returns true if the value on the left is exactly equal to the value on the right, and false otherwise. In this case, it checks whether the borough field of each permit is "Manhattan."

Run the script and check the output.

node fetch-data.js

You should see how many of your 25 permits are in Manhattan.

14

Part 5: Saving data

You've explored the data enough. Now let's trim down fetch-data.js so it downloads the permits and saves them to a file that your page can use.

Remove all the console.log and exploration lines. Replace the contents of fetch-data.js with this:

import fs from "fs";
import path from "node:path";

const url = "https://data.cityofnewyork.us/resource/tg4x-b46p.json?$where=eventtype=%27Shooting%20Permit%27&$order=startdatetime,eventid%20DESC&$limit=25";
const response = await fetch(url);
const permits = await response.json();

console.log(`Fetched ${permits.length} permits.`);

const dataDir = path.join("src", "lib", "data");
fs.mkdirSync(dataDir, { recursive: true });

const outputPath = path.join(dataDir, "permits.json");
fs.writeFileSync(outputPath, JSON.stringify(permits, null, 2));
console.log(`Saved to ${outputPath}`);

Let's walk through the new lines.

The first two lines import Node's built-in fs (file system) and path modules to read and write files on your hard drive.

The dataDir variable uses path.join() to build a file path from separate folder names. You could write "src/lib/data" as a plain string instead, but path.join handles the differences between operating systems — Windows uses backslashes, Mac and Linux use forward slashes — so your script works everywhere.

The fs.mkdirSync() call creates that directory if it doesn't already exist. The { recursive: true } option tells it to create any missing parent folders along the way, so you don't get an error if src/lib hasn't been created yet. The Sync suffix means the function finishes before the next line executes, which is the simple behavior you'd expect, and similar to how await works with other functions.

The outputPath variable uses path.join again to build the full path to the file you want to write: src/lib/data/permits.json.

The fs.writeFileSync() call writes data to that file. The first argument is the path, and the second is the content to write. Since permits is a JavaScript array, we need to convert it to a text string first with JSON.stringify(). The extra arguments null, 2 tell it to format the output with two-space indentation, so the file is human-readable when you open it in your editor.

Run it.

node fetch-data.js

You should see a message confirming how many permits were fetched and where the file was saved. Open src/lib/data/permits.json in Visual Studio Code and take a look. You'll see an array of 25 permit objects.

Visual Studio Code showing permits.json with an array of film permit objects, each containing fields like eventid, eventtype, borough and subcategoryname

Homework

Task 1: Fetch your own dataset

Remember the dataset you pitched from the NYC Open Data portal in your earlier homework?

Rewrite the fetch-data.js script in this repository to download the dataset you pitched last week and save it to src/lib/data/ as a JSON file. Use the same pattern you followed in today's lesson.

Your script should run cleanly with node fetch-data.js and produce a readable JSON file. Send me the link to the data file your GitHub repository.