Get Street running in under 60 seconds — prerequisites, install, configure, and run.
This guide walks you through getting a street server running from zero, explains every configuration decision, and prepares you for production deployment.
| Requirement | Minimum Version | Notes |
|---|---|---|
| Node.js | 20.0.0 | Uses node:test, --test, top-level await |
| npm | 9.0.0 | Workspaces support |
| TypeScript | 5.4.0 | NodeNext module resolution |
| PostgreSQL | 14.0 | Wire protocol v3 (used since PG 7.4) |
Check your versions:
1
2
3
node --version # v20.x.x or higher
npm --version # 9.x.x or higher
psql --version # psql (PostgreSQL) 14.x or higher
1
2
git clone https://github.com/hassanmubiru/street.git my-api
cd my-api
1
2
mkdir my-api && cd my-api
npm init -y
Then copy package.json and tsconfig.json from this repo.
street has exactly two runtime dependencies:
1
2
npm install reflect-metadata ws
npm install --save-dev typescript @types/node @types/ws
Why only two? Everything else — HTTP server, TLS, streams, crypto, cluster — ships with Node.js. External abstractions introduce version skew and CVE surfaces. street keeps the dependency tree auditable at a glance.
reflect-metadata — Enables TypeScript’s emitDecoratorMetadata to record constructor parameter types at runtime. This is the only way to perform constructor injection without explicit token registration.
ws — A battle-tested, low-level WebSocket implementation. Node’s built-in http.Server supports upgrades but not the WebSocket framing protocol itself.
1
2
3
4
5
# Using the provided script (recommended)
npm install && npx tsc
# Or manually
npx tsc
The framework ships with a street-build.sh script that runs npm ci and npx tsc. You can use it from the core package:
1
2
cd packages/core
bash street-build.sh
Or build manually:
1
2
3
npm ci
npx tsc
mkdir -p dist/uploads
TypeScript source in src/ and tests/ is compiled to dist/. The output mirrors the source directory:
1
2
3
src/main.ts → dist/main.js
src/http/server.ts → dist/http/server.js
tests/integration.test.ts → dist/tests/integration.test.js
All imports use .js extensions even in .ts source files — this is required by the NodeNext module resolution standard. The TypeScript compiler resolves .ts files when encountering .js imports during compilation.
Create a .env file (never commit this):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Server
PORT=3000
HOST=0.0.0.0
NODE_ENV=development
# Database
PG_HOST=localhost
PG_PORT=5432
PG_DATABASE=myapp_dev
PG_USER=myapp
PG_PASSWORD=your-database-password
# Security — MUST be changed in production
JWT_SECRET=replace-with-random-32-plus-char-string-here!!
SESSION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# Optional directories
UPLOADS_DIR=./uploads
MIGRATIONS_DIR=./migrations
Load it before starting:
1
2
3
4
5
# Using dotenv (dev only, not required in production)
node --env-file=.env dist/main.js
# Or export manually
export $(cat .env | xargs) && node dist/main.js
The session key must be a 64-character hex string representing 32 random bytes:
1
2
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# e.g.: a3f8c2d1e9b047623a5f18d7e4c0b291f8e3a72d10c5b468f92e1d3a07b4c985
1
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
1
2
3
createdb myapp_dev
# or
psql -c "CREATE DATABASE myapp_dev;"
Run migrations to create the schema:
1
2
3
4
5
6
node dist/main.js migrate
# [migrations] Applying: 001_create_users.sql
# [migrations] Applied: 001_create_users.sql
# [migrations] Applying: 002_create_sessions_webhooks.sql
# [migrations] Applying: 002_create_sessions_webhooks.sql
# [migrations] All migrations complete.
1
2
node dist/main.js
# [street] Listening on http://0.0.0.0:3000
Test it:
1
2
curl http://localhost:3000/api/health
# {"status":"ok","uptime":2.1,"pid":12345,...}
During development, rebuild on every save:
1
2
3
4
5
# Terminal 1: Watch TypeScript
npx tsc --watch
# Terminal 2: Run server (auto-restarts with Node 20 --watch)
node --watch dist/src/main.js
Or combine with a single command:
1
npx tsc && node dist/src/main.js
The tsconfig.json uses strict settings that enforce production-quality code:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
| Option | Why it matters |
|---|---|
"module": "NodeNext" |
Enables ESM with explicit .js extensions — matches Node 20 native ESM |
"moduleResolution": "NodeNext" |
Required companion to NodeNext module |
"strict": true |
Enables all strict checks as a group |
"noUnusedLocals": true |
Prevents dead code accumulation |
"experimentalDecorators": true |
Required for @Controller, @Injectable etc. |
"emitDecoratorMetadata": true |
Emits design:paramtypes metadata — required for constructor injection |
Why emitDecoratorMetadata matters: Without it, Reflect.getMetadata('design:paramtypes', MyService) returns undefined and the IoC container cannot resolve constructor dependencies automatically.
Run the integration test suite against a live PostgreSQL instance:
1
2
3
4
PG_HOST=localhost PG_USER=myapp PG_PASSWORD=secret PG_DATABASE=myapp_dev \
JWT_SECRET=test-secret-at-least-32-chars-here \
SESSION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))") \
node --test dist/tests/integration.test.js
Expected output:
1
2
3
4
5
6
7
8
9
▶ IoC Container
✔ resolves a class with no dependencies (1.2ms)
✔ resolves nested dependencies (0.8ms)
✔ returns singleton on repeated resolve (0.3ms)
...
▶ PostgreSQL Wire Protocol
✔ connects to PostgreSQL (45ms)
✔ executes a simple query (3ms)
...
All 13 test suites should pass.
Cannot find module 'reflect-metadata'1
npm install reflect-metadata
Make sure import 'reflect-metadata' is the first line of your entry point (src/main.ts). It must execute before any decorator runs.
SyntaxError: Cannot use import statementYour code is running as CommonJS. Check:
package.json has "type": "module"tsconfig.json has "module": "NodeNext"node dist/... not ts-node without ESM configError: PostgreSQL connection timeoutCheck that PostgreSQL is running and the credentials match:
1
psql -h localhost -U myapp -d myapp_dev -c "SELECT 1"
If connecting to Docker:
1
2
3
4
5
6
7
docker run -d \
--name pg \
-e POSTGRES_USER=myapp \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=myapp_dev \
-p 5432:5432 \
postgres:16-alpine
error TS2339: Property 'defineMetadata' does not exist on type 'typeof Reflect'Add the reflect-metadata type shim. Create src/reflect-shim.d.ts:
1
2
3
4
declare namespace Reflect {
function defineMetadata(key: unknown, value: unknown, target: object): void;
function getMetadata(key: unknown, target: object): unknown;
}
This is included in the framework source and loaded automatically via tsconfig.json’s include glob.