Local Development Guide
February 14, 2026 · View on GitHub
Complete guide to setting up and running the Perspectize backend locally.
Prerequisites
Required Software
| Tool | Version | Installation |
|---|---|---|
| Go | 1.25+ | go.dev/dl |
| PostgreSQL | 18+ | Docker recommended (see below) |
| Docker | 24+ | docker.com |
| Make | Any | Usually pre-installed on Mac/Linux |
| golang-migrate | v4.17+ | go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest |
Optional Tools
| Tool | Purpose | Installation |
|---|---|---|
| golangci-lint | Linting | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest |
| air | Hot reload | go install github.com/air-verse/air@latest |
| DBeaver | DB GUI | dbeaver.io |
Quick Start
# 1. Clone repository
git clone https://github.com/CodeWarrior-debug/perspectize.git
cd perspectize/backend
# 2. Start PostgreSQL (Docker)
make docker-up
# 3. Copy environment file
cp .env.example .env
# Edit .env with your settings
# 4. Install dependencies
go mod download
# 5. Run migrations
make migrate-up
# 6. Start the server
make run
# Server running at http://localhost:8080
# GraphQL Playground at http://localhost:8080/graphql
Detailed Setup
1. Database Setup
Option A: Docker Compose (Recommended)
# docker-compose.yml
services:
postgres:
image: postgres:18-alpine
container_name: perspectize-db
environment:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U testuser"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
# Start database
make docker-up
# Verify connection
docker exec -it perspectize-db psql -U testuser -d testdb -c "SELECT version();"
Option B: Local PostgreSQL
# macOS (Homebrew)
brew install postgresql@18
brew services start postgresql@18
# Create database
createdb testdb
createuser -s testuser
2. Environment Configuration
Create .env file in backend/:
# Database
DATABASE_URL=postgres://testuser:testpass@localhost:5432/testdb?sslmode=disable
# YouTube API (optional for basic development)
YOUTUBE_API_KEY=your_youtube_api_key_here
# Server
PORT=8080
ENV=development
# Logging
LOG_LEVEL=debug
LOG_FORMAT=text # text or json
3. Database Migrations
# Apply all migrations
make migrate-up
# Rollback last migration
make migrate-down
# Check migration status
make migrate-version
# Create new migration
make migrate-create
# Then enter migration name when prompted
Migration File Structure
-- migrations/000001_initial_schema.up.sql
CREATE TABLE IF NOT EXISTS content (
id SERIAL PRIMARY KEY,
url VARCHAR(2048) UNIQUE,
name VARCHAR(255) NOT NULL UNIQUE,
length INTEGER,
length_units VARCHAR(50),
content_type VARCHAR(100) NOT NULL,
response JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_content_name ON content(name);
CREATE INDEX idx_content_type ON content(content_type);
-- migrations/000001_initial_schema.down.sql
DROP TABLE IF EXISTS content;
4. Running the Server
Standard Mode
make run
# or
go run cmd/server/main.go
Hot Reload Mode (Development)
# Install air if not already
go install github.com/air-verse/air@latest
# Run with hot reload
make dev
With Specific Flags
# Custom port
PORT=3000 make run
# Debug logging
LOG_LEVEL=debug make run
# Production mode locally
ENV=production make run
5. Running Tests
# All unit tests
make test
# With coverage
make test-coverage
# View report: open coverage.html
# Specific package
go test -v ./internal/core/services/...
# Single test
go test -v -run TestFunctionName ./path/to/package
# Race detection
go test -race ./...
Project Structure
backend/
├── cmd/
│ └── server/
│ └── main.go # Application entry point, DI wiring
├── internal/ # Private application code
│ ├── config/
│ │ └── config.go # Environment configuration
│ ├── core/ # Domain layer (hexagonal core)
│ │ ├── domain/ # Domain models and entities
│ │ ├── ports/ # Interface definitions
│ │ │ ├── repositories/ # Repository interfaces
│ │ │ └── services/ # Service interfaces
│ │ └── services/ # Business logic
│ ├── adapters/ # Infrastructure layer
│ │ ├── graphql/ # GraphQL resolvers (primary adapter)
│ │ ├── repositories/ # PostgreSQL (secondary adapter)
│ │ └── youtube/ # YouTube API (secondary adapter)
│ └── middleware/ # HTTP middleware
├── pkg/ # Public utilities
│ └── database/
│ └── connection.go # DB connection management
├── migrations/ # SQL migrations
│ ├── 000001_initial_schema.up.sql
│ └── 000001_initial_schema.down.sql
├── test/
│ ├── integration/ # Integration tests
│ └── fixtures/ # Test data
├── schema.graphql # GraphQL schema
├── gqlgen.yml # gqlgen configuration
├── .env.example # Environment template
├── .golangci.yml # Linter configuration
├── docker-compose.yml # Local services
├── Makefile # Build commands
├── go.mod
└── go.sum
Common Tasks
Adding a New API Endpoint
-
Define the domain model (if new):
// internal/core/domain/user_preference.go type UserPreference struct { ID int UserID int Theme string CreatedAt time.Time } -
Create port interface:
// internal/core/ports/repositories/user_preference_repository.go type UserPreferenceRepository interface { GetByUserID(ctx context.Context, userID int) (*domain.UserPreference, error) Upsert(ctx context.Context, pref *domain.UserPreference) error } -
Create service layer:
// internal/core/services/user_preference_service.go type UserPreferenceService struct { repo ports.UserPreferenceRepository } -
Implement repository adapter:
// internal/adapters/repositories/postgres/user_preference_repository.go -
Update GraphQL schema and regenerate:
make graphql-gen -
Implement resolver:
// internal/adapters/graphql/resolvers/user_preference_resolver.go -
Wire in main.go:
// cmd/server/main.go -
Write tests
Adding a GraphQL Query/Mutation
-
Update schema:
# schema.graphql type Query { userPreferences(userID: ID!): UserPreferences } type UserPreferences { id: ID! theme: String! } -
Regenerate code:
make graphql-gen -
Implement resolver:
// internal/adapters/graphql/resolvers/resolver.go func (r *queryResolver) UserPreferences(ctx context.Context, userID string) (*model.UserPreferences, error) { // Implementation }
Debugging
Database Queries
# Enable query logging in .env
LOG_LEVEL=debug
# Or use psql directly
docker exec -it perspectize-db psql -U testuser -d testdb
# View slow queries
SELECT query, calls, mean_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;
Request Tracing
// Check logs for request_id field
{"level":"info","request_id":"abc123","method":"POST","path":"/graphql"}
Troubleshooting
Database Connection Issues
# Check if PostgreSQL is running
docker ps | grep perspectize-db
# Check connection
psql $DATABASE_URL -c "SELECT 1"
# Reset database
docker compose down -v
make docker-up
make migrate-up
Port Already in Use
# Find process using port
lsof -i :8080
# Kill it
kill -9 <PID>
# Or use different port
PORT=3000 make run
Migration Failures
# Check current version
make migrate-version
# Force to specific version (careful!)
make migrate-force
# Enter version when prompted
# Start fresh
make migrate-down # Repeat until at version 0
make migrate-up
Go Module Issues
# Clean module cache
go clean -modcache
# Re-download
go mod download
# Tidy
go mod tidy
IDE Setup
VS Code
Recommended extensions:
- Go (official)
- Go Test Explorer
- PostgreSQL (by Chris Kolkman)
- Thunder Client (API testing)
- GraphQL (for schema syntax highlighting)
Settings (.vscode/settings.json):
{
"go.useLanguageServer": true,
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"editor.formatOnSave": true,
"[go]": {
"editor.defaultFormatter": "golang.go"
}
}
GoLand
- Enable "Go Modules" integration
- Set GOROOT to your Go installation
- Configure database data source for auto-completion
Next Steps
- Review
.docs/ARCHITECTURE.mdfor system design - Check
.docs/AGENTS.mdfor AI agent routing - Run
make testto ensure everything works