Database

Schemas & Access Patterns

Detailed schemas for the key DynamoDB tables, the GSIs that power secondary queries, and how to use the db module in your code.


Key Table Schemas

biztechEvents

Keys: PK = id (string), SK = year (number)

FieldTypeDescription
idstringURL-safe slug (e.g., "blueprint", "kickstart")
yearnumberEvent year (e.g., 2026)
enamestringDisplay name (e.g., "Blueprint 2026")
descriptionstringFull description
capacnumberMaximum capacity
startDatestringISO date string
endDatestringISO date string
elocationstringVenue name
imageUrlstringEvent banner image URL (S3)
isPublishedbooleanVisible to non-admin users
isCompletedbooleanEvent has concluded
isApplicationBasedbooleanRequires application to register
pricingobject{ nonMember: number, member: number }
registrationQuestionsarrayCustom form questions
feedbackQuestionsarrayPost-event feedback questions

Access Pattern: Most queries use id + year composite key. List all events uses a full table scan.


biztechUsers

Keys: PK = id (string = email)

FieldTypeDescription
idstringEmail address (primary key)
fnamestringFirst name
lnamestringLast name
emailstringSame as id
adminbooleanAuto-set from email domain
isMemberbooleanHas paid membership
facultystringFaculty/school
majorstringArea of study
yearstringAcademic year (e.g., "3rd Year")
studentIdstringStudent number
dietstringDietary restrictions
genderstringGender identity
educationstringUndergrad, Grad, etc.
favEventsarrayList of favorited event IDs

biztechRegistrations

Keys: PK = id (string = email), SK = eventID;year (string, e.g., "blueprint;2026")

FieldTypeDescription
idstringUser's email
eventID;yearstringComposite event key
registrationStatusstringOne of the registration statuses (see below)
basicInformationobject{ fname, lname }
dynamicResponsesobjectAnswers to custom questions
isPartnerbooleanPartner vs. attendee registration
applicationStatusstringFor application-based events
checkoutLinkstringStripe payment link (if paid event)
pointsnumberPoints earned at event
scannedQRsarrayQR codes the user has scanned
teamIDstringTeam assignment
createdAtnumberTimestamp of registration

Registration Statuses: registered, waitlist, checkedIn, incomplete, cancelled, accepted, acceptedPending, acceptedComplete, rejected


biztechMembers2026

Keys: PK = id (string = email) GSI: profileIDQueryIndex on profileID field

FieldTypeDescription
idstringEmail address
profileIDstringHuman-readable profile ID
firstNamestringFirst name
pronounsstringPreferred pronouns
facultystringFaculty
majorstringMajor
yearstringAcademic year
internationalbooleanInternational student
prevMemberbooleanReturning member
educationstringEducation level
heardFromstringHow they heard about BizTech
topicsarrayTopics of interest
adminbooleanAdmin flag
discordIdstringDiscord user ID

Table Name History

The members table is named biztechMembers2026 because it was created for the 2025-2026 membership year. Previous years had their own tables (biztechMembers2025, etc.). The current table name is set in constants/tables.js.


biztechProfiles

Keys: PK = id (string), SK = eventID;year (string)

This table stores both profiles and connections:

  • Profile items: id = email-derived ID, eventID;year = event context
  • Connection items: Stored as separate items in the same table with SK = connection;{otherProfileID}
FieldTypeDescription
profileIDstringHuman-readable ID (e.g., "SillyPandasDeny")
profileTypestringEXEC, ATTENDEE, or COMPANY
fname / lnamestringName
pronounsstringPronouns
major / yearstringAcademic info
hobby1 / hobby2stringHobbies
linkedinstringLinkedIn URL
profilePictureURLstringS3 URL
descriptionstringBio text
viewableMapobjectVisibility controls for each field
connectionsarrayList of connected profile IDs
embeddingarrayOpenAI embedding vector (1536 dimensions)

biztechTeams

Keys: PK = eventID;year (string), SK = id (string = team ID)

FieldTypeDescription
eventID;yearstringEvent composite key
idstringTeam ID
teamNamestringDisplay name
memberEmailsarrayTeam member emails
memberNamesarrayTeam member names
pointsnumberTotal points earned
pointsSpentnumberPoints redeemed
scannedQRsarrayQR codes scanned
inventoryarrayItems/prizes purchased
submissionstringProject submission link
fundingnumberInvestment funding received

Global Secondary Indexes (GSIs)

GSIs let you query tables by fields other than the primary key.

TableIndex NameKey SchemaUsed For
biztechMembers2026profileIDQueryIndexPK: profileIDLook up member email by profile ID
biztechInvestmentsteamInvestmentsIndexPK: teamIdGet all investments for a team
biztechInvestmentsinvestorInvestmentsIndexPK: investorEmailGet investor's portfolio
bizWallSocketsbyEventPK: eventKeyGet wall connections per event
bizBtxProjectsbyEventPK: eventKeyGet projects per event
bizBtxHoldingsbyProjectPK: projectIdGet all holders of a project
bizBtxSocketsbyEventPK: eventKeyGet WebSocket connections per event

Common Access Patterns

What You NeedHow to Get It
Get a specific eventdb.getOne(eventId, EVENTS_TABLE, { year })
List all eventsdb.scan(EVENTS_TABLE)
Get user's registrationsdb.scan(REGISTRATIONS_TABLE, { id: email })
Get event's registrationsdb.scan(REGISTRATIONS_TABLE, { "eventID;year": key })
Get a userdb.getOne(email, USERS_TABLE)
Get a memberdb.getOne(email, MEMBERS_TABLE)
Get teams for an eventdb.query({ KeyConditionExpression: "eventID;year = :ey" })
Look up member by profileIDQuery the profileIDQueryIndex GSI

Working with the DB Module

Creating an item

import db from "../../lib/db.js";
import { EVENTS_TABLE } from "../../constants/tables.js";

// This uses PutItem with a condition that the item doesn't already exist
await db.create({
  id: "my-event",
  year: 2026,
  ename: "My Event",
}, EVENTS_TABLE);

Reading an item

// Single key
const user = await db.getOne(email, USERS_TABLE);

// Composite key (PK + SK)
const event = await db.getOne("blueprint", EVENTS_TABLE, { year: 2026 });

Updating an item

// db.updateDB handles reserved words automatically
await db.updateDB(email, { isMember: true, year: "3rd Year" }, USERS_TABLE);

Scanning (listing all items)

// Full scan
const allEvents = await db.scan(EVENTS_TABLE);

// Filtered scan
const activeEvents = await db.scan(EVENTS_TABLE, {
  FilterExpression: "isPublished = :pub",
  ExpressionAttributeValues: { ":pub": true },
});

Scans Are Expensive

DynamoDB scans read the entire table. This is fine for small tables but should be avoided for large ones. Prefer query with key conditions when possible.


Local DynamoDB

For local development, we use DynamoDB Local (a Java application that emulates DynamoDB).

Seeding local data

node scripts/initlocal.js

This script reads the actual table schemas and data from AWS and recreates them locally. You need valid AWS credentials for this.

Viewing local data

You can use the AWS CLI to query local DynamoDB:

# List tables
aws dynamodb list-tables --endpoint-url http://localhost:8000

# Scan a table
aws dynamodb scan --table-name biztechEvents --endpoint-url http://localhost:8000

Or use a GUI like NoSQL Workbench.

Previous
Overview & Tables