← back to Norma
app/api/census/route.ts
64 lines
import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/require-role';
const CENSUS_BASE = 'https://api.census.gov/data/2022/acs/acs5';
/**
* GET /api/census?action=zip&zip=90210
* Fetch American Community Survey data by ZIP code.
* Returns population, median income, education, debt proxies.
*/
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const apiKey = process.env.CENSUS_API_KEY;
if (!apiKey) {
return NextResponse.json({ error: 'CENSUS_API_KEY not configured (free at api.census.gov)' }, { status: 503 });
}
const { searchParams } = new URL(request.url);
const action = searchParams.get('action') || 'zip';
try {
if (action === 'zip') {
const zip = searchParams.get('zip');
if (!zip || !/^\d{5}$/.test(zip)) {
return NextResponse.json({ error: 'Valid 5-digit ZIP required' }, { status: 400 });
}
// Key ACS variables for advocacy:
// B01003_001E = total population
// B19013_001E = median household income
// B15003_022E = bachelor's degree
// B15003_023E = master's degree
// B25077_001E = median home value
const vars = ['NAME', 'B01003_001E', 'B19013_001E', 'B15003_022E', 'B15003_023E', 'B25077_001E'].join(',');
const url = `${CENSUS_BASE}?get=${vars}&for=zip%20code%20tabulation%20area:${zip}&key=${apiKey}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`Census returned ${res.status}`);
const data = await res.json();
if (!Array.isArray(data) || data.length < 2) {
return NextResponse.json({ error: 'No data returned for ZIP' }, { status: 404 });
}
const row = data[1];
return NextResponse.json({
zip,
name: row[0],
population: parseInt(row[1], 10) || 0,
median_income: parseInt(row[2], 10) || 0,
bachelors_degree_count: parseInt(row[3], 10) || 0,
masters_degree_count: parseInt(row[4], 10) || 0,
median_home_value: parseInt(row[5], 10) || 0,
});
}
return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
} catch (err) {
return NextResponse.json({ error: (err as Error).message }, { status: 500 });
}
}