University of Washington Tacoma
School of Engineering and Technology
Computer Science and Systems
TCSS 460 A - Client/Server Programming for Internet Applications
Autumn 2025
Instructor: Professor Charles Bryan
Email: cfb3@uw.edu
A production-ready RESTful API for managing message entries with priority levels and API key authentication. Built with Node.js, Express, TypeScript, and PostgreSQL following professional best practices and educational clarity for senior-level computer science coursework.
- Complete CRUD Operations - Create, read, update, and delete messages
- Priority Management - Three-level priority system (1=high, 2=medium, 3=low)
- API Key Authentication - Secure protected endpoints with API key validation
- Consistent Response Structure - Standardized
MessageEntryformat across all endpoints - Comprehensive Documentation - Swagger/OpenAPI spec and educational guides
- Full Test Coverage - 68 automated Postman/Newman test assertions
- Type Safety - Full TypeScript implementation with strict typing
- Input Validation - Express-validator middleware for request validation
- Error Handling - Standardized error codes and descriptive messages
- Docker Integration - Containerized PostgreSQL database
- API Documentation (Swagger UI): http://localhost:8000/api-docs
- Educational Documentation: http://localhost:8000/doc
- Generate API Key: http://localhost:8000/api-key
- Health Check: http://localhost:8000/health
- Quick Start
- API Endpoints
- Authentication
- Project Structure
- Database Schema
- Testing
- Development
- Educational Objectives
- Node.js (v18 or later)
- Docker and Docker Compose
- npm or yarn
-
Clone the repository
git clone <repository-url> cd TCSS-460-message-api
-
Install dependencies
npm install
-
Set up environment variables
Create
.env.developmentfile:NODE_ENV=development PORT=8000 # Database Configuration DB_HOST=localhost DB_PORT=5433 DB_USER=postgres DB_PASSWORD=password DB_NAME=message_db
-
Start the application
Option A: Start everything with one command (recommended)
npm run start:full
This starts Docker and the API server automatically.
Option B: Start manually in separate steps
# Start PostgreSQL database npm run docker:up # Then start the development server npm run dev # or with nodemon for hot reload: npm run local
-
Access the API
- API: http://localhost:8000
- Swagger UI: http://localhost:8000/api-docs
- Documentation: http://localhost:8000/doc
# Development
npm run start:full # Start Docker + API (one command startup)
npm run dev # Start development server
npm run local # Start with nodemon (hot reload)
npm run build # Compile TypeScript to JavaScript
npm start # Run compiled production build
# Database
npm run docker:up # Start PostgreSQL container
npm run docker:down # Stop and remove PostgreSQL container
# Testing
npm test # Run Jest unit tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Generate coverage report
# Quality Checks
npm run typecheck # Run TypeScript type checking
npm run typecheck:watch # Type check in watch mode
npm run lint # Run ESLintGET /health- Server health statusGET /api-key- API key generation form (HTML)POST /api-key- Generate new API key
POST /message- Create new messageGET /message?priority={1-3}- Get messages by priority levelGET /message/all- Get all messages with statisticsGET /message/{name}- Get specific message by sender namePATCH /message- Update existing message contentDELETE /message?priority={1-3}- Delete all messages with priorityDELETE /message/{name}- Delete specific message by name
All protected endpoints require the x-api-key header:
curl -H "x-api-key: your-api-key-here" http://localhost:8000/protected/message/allPOST /protected/messageGET /protected/message?priority={1-3}GET /protected/message/allGET /protected/message/{name}PATCH /protected/messageDELETE /protected/message?priority={1-3}DELETE /protected/message/{name}
All endpoints return consistent JSON responses:
{
"success": true,
"message": "Operation description",
"data": {
"name": "John Doe",
"message": "Hello World",
"priority": 1,
"formatted": "{1} - [John Doe] says: Hello World"
},
"timestamp": "2025-10-11T10:30:00.000Z"
}{
"success": false,
"message": "Error description",
"errorCode": "MSG_NAME_EXISTS",
"timestamp": "2025-10-11T10:30:00.000Z"
}curl -X POST http://localhost:8000/message \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"message": "Hello World",
"priority": 1
}'curl http://localhost:8000/message?priority=1curl -X PATCH http://localhost:8000/message \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"message": "Updated message content"
}'curl -X DELETE http://localhost:8000/message/John%20Doe-
Using the Web Form:
- Visit http://localhost:8000/api-key
- Enter your name and email
- Copy the generated API key
-
Using cURL:
curl -X POST http://localhost:8000/api-key \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe", "email": "john@example.com" }'
Add the x-api-key header to your requests:
curl -H "x-api-key: your-api-key-here" \
http://localhost:8000/protected/message/allAPI keys are:
- 48 characters long
- Securely hashed in the database
- Tracked for usage statistics
- Can be activated/deactivated
tcss-460-message-api/
├── src/
│ ├── index.ts # Server entry point with lifecycle management
│ ├── app.ts # Express app configuration
│ ├── types/ # TypeScript type definitions
│ │ ├── messageTypes.ts # Message-related types
│ │ ├── apiKeyTypes.ts # API key types
│ │ ├── apiTypes.ts # API response types
│ │ └── errorTypes.ts # Error code definitions
│ ├── controllers/ # Business logic
│ │ ├── messageController.ts # Message CRUD operations
│ │ ├── apiKeyController.ts # API key generation
│ │ └── documentationController.ts # Documentation routes
│ ├── routes/ # Express routes
│ │ ├── index.ts # Main router
│ │ ├── open/ # Public routes
│ │ └── closed/ # Protected routes
│ ├── core/
│ │ ├── middleware/ # Express middleware
│ │ │ ├── messageValidation.ts # Message validation rules
│ │ │ ├── apiKeyValidation.ts # API key validation rules
│ │ │ └── apiKeyAuth.ts # Authentication middleware
│ │ └── utilities/ # Helper functions
│ │ ├── database.ts # PostgreSQL connection pool
│ │ ├── envConfig.ts # Environment configuration
│ │ ├── responseUtils.ts # Response formatting
│ │ ├── validationUtils.ts # Input validation
│ │ ├── apiKeyUtils.ts # API key generation/hashing
│ │ └── markdownUtils.ts # Markdown processing
│ └── test/ # Test configuration
│ └── setup.ts # Jest setup
├── data/
│ └── init.sql # Database schema initialization
├── docs/
│ └── swagger.yaml # OpenAPI 3.0 specification
├── testing/
│ └── postman/ # Postman collections
│ └── TCSS-460-Message-API-Complete.postman_collection.json
├── ai.prof/ # AI-assisted development documentation
│ ├── instructions.md # Development guidelines
│ └── database-mocking-strategies.md # Testing strategies
├── docker-compose.yml # PostgreSQL container config
├── jest.config.js # Jest configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies and scripts
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
message TEXT NOT NULL,
priority INTEGER NOT NULL CHECK (priority >= 1 AND priority <= 3),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);Constraints:
namemust be unique (serves as natural key)prioritymust be 1, 2, or 3messagecannot be empty
Indexes:
- Primary key on
id - Unique index on
name - Index on
priorityfor filtering queries
CREATE TABLE api_keys (
id SERIAL PRIMARY KEY,
api_key VARCHAR(64) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
email VARCHAR(255),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMP WITH TIME ZONE,
request_count INTEGER DEFAULT 0
);Features:
- API keys are hashed using SHA-256 before storage
- Tracks usage statistics (request count, last used)
- Can be activated/deactivated
- Email is optional
The project includes a comprehensive Postman collection with 68 automated test assertions.
Run with Newman:
# Install Newman globally
npm install -g newman
# Run the collection
newman run testing/postman/TCSS-460-Message-API-Complete.postman_collection.json \
--delay-request 200Test Coverage:
- Health checks and API key generation
- All CRUD operations (public endpoints)
- Priority-based filtering
- Validation error handling
- Authentication tests
- Protected endpoint operations
- Documentation endpoint verification
Results: 68/68 assertions passing ✅
# Run all tests
npm test
# Run in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverageCurrent test files:
src/core/utilities/__tests__/validationUtils.test.tssrc/core/utilities/__tests__/apiKeyUtils.test.ts
Testing Strategy Documentation:
See ai.prof/database-mocking-strategies.md for comprehensive testing strategies including:
- pg-mem (in-memory PostgreSQL)
- Testcontainers (real PostgreSQL in Docker)
- Docker Compose test environments
- Fixture and factory patterns
The API uses environment-specific configuration files:
.env.development- Local development.env.test- Testing environment.env.production- Production deployment
Required Environment Variables:
NODE_ENV=development
PORT=8000
# Database
DB_HOST=localhost
DB_PORT=5433
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=message_dbStart database:
npm run docker:upStop database:
npm run docker:downDatabase connection:
- Host: localhost
- Port: 5433 (mapped from container's 5432)
- Database: message_db
- User: postgres
- Password: password
Reset database:
npm run docker:down
npm run docker:up
# The init.sql script runs automaticallyThe project uses TypeScript path aliases for clean imports:
import { sendSuccess } from '@utilities/responseUtils';
import { MessageEntry } from '@/types';
import { getPool } from '@db';Configured paths:
@/*→src/*@utilities/*→src/core/utilities/*@db→src/core/utilities/database
Conventions:
- Descriptive variable names for educational clarity
- Functional expression-based coding when readable
- Comprehensive JSDoc comments for all functions
- Async/await for asynchronous operations
- Express middleware for cross-cutting concerns
Example:
/**
* Create a new message entry in the database
* Input validation handled by validateCreateMessage middleware
*
* @param request - Express request with validated message data in body
* @param response - Express response for sending results
* @returns Promise<void>
* @throws Will send 400 error if name already exists
*/
export const createMessage = async (
request: Request,
response: Response
): Promise<void> => {
// Implementation...
};This project demonstrates professional software engineering practices:
1. RESTful API Design
- Resource-based URL structure
- HTTP method semantics (GET, POST, PATCH, DELETE)
- Proper status codes (200, 201, 400, 401, 404, 500)
- Consistent response formats
2. TypeScript in Node.js
- Interface design and type safety
- Path mapping and module resolution
- Strict typing benefits
- Type-driven development
3. Database Integration
- PostgreSQL connection pooling
- SQL query parameterization (injection prevention)
- Transaction management
- Schema design and constraints
4. Authentication & Security
- API key generation and hashing
- Authentication middleware patterns
- Protected vs. public routes
- Secure credential storage
5. Input Validation
- Express-validator middleware
- Request sanitization
- Error response formatting
- Validation chain patterns
6. Testing Strategies
- End-to-end testing with Postman/Newman
- Automated test assertions
- Test data management
- Database mocking strategies (documented)
7. Docker & Containers
- Docker Compose for local development
- Container networking
- Volume management
- Environment isolation
8. Code Organization
- Layered architecture (routes → controllers → utilities)
- Separation of concerns
- Barrel exports for clean imports
- Modular design patterns
9. Documentation
- OpenAPI/Swagger specification
- Interactive API documentation
- Educational guides and examples
- In-code JSDoc comments
10. Professional Development Practices
- Git workflow and meaningful commits
- Environment-based configuration
- Error handling patterns
- Code reusability
Why PostgreSQL over MongoDB?
- Teaches relational database concepts
- ACID compliance for data integrity
- Schema enforcement
- SQL query skills
Why Express over other frameworks?
- Industry standard for Node.js APIs
- Minimal abstraction shows underlying concepts
- Extensive middleware ecosystem
- Clear request/response flow
Why TypeScript?
- Catches errors at compile time
- Better IDE support and autocomplete
- Self-documenting code with types
- Industry trend for large applications
Why name as natural key?
- Demonstrates alternative to surrogate keys
- More intuitive for API consumers
- Teaches uniqueness constraints
- Security: no database ID exposure
Before deploying to production:
-
Environment Variables
- Use production database credentials
- Set
NODE_ENV=production - Configure proper PORT binding
- Use secrets management (not
.envfiles)
-
Security Enhancements
- Enable CORS with proper origins
- Add rate limiting
- Implement request logging
- Use HTTPS/TLS
- Add security headers (helmet)
-
Database
- Use managed PostgreSQL (RDS, Cloud SQL, etc.)
- Enable SSL connections
- Set up automated backups
- Configure connection pooling limits
-
Monitoring
- Add application performance monitoring (APM)
- Set up error tracking (Sentry, etc.)
- Configure health check monitoring
- Log aggregation and analysis
-
Build Process
npm run build NODE_ENV=production npm start
- Swagger UI:
/api-docs- Interactive API documentation - Educational Docs:
/doc- HTML guide with code examples - Database Strategies:
ai.prof/database-mocking-strategies.md - Development Guidelines:
ai.prof/instructions.md
# Type check without running
npm run typecheck
# Build and check for errors
npm run build
# Clean build artifacts
rm -rf dist/
# View database logs
docker logs message-api-postgres
# Connect to database CLI
docker exec -it message-api-postgres psql -U postgres -d message_db# Complete workflow example
# 1. Create message
curl -X POST http://localhost:8000/message \
-H "Content-Type: application/json" \
-d '{"name":"TestUser","message":"Hello","priority":1}'
# 2. Get all messages
curl http://localhost:8000/message/all
# 3. Get by priority
curl http://localhost:8000/message?priority=1
# 4. Update message
curl -X PATCH http://localhost:8000/message \
-H "Content-Type: application/json" \
-d '{"name":"TestUser","message":"Updated"}'
# 5. Delete message
curl -X DELETE http://localhost:8000/message/TestUserThis project is designed for educational purposes. When contributing:
- Follow the existing code style
- Add JSDoc comments for new functions
- Update tests for new features
- Keep educational clarity as a priority
- Document design decisions
This project is created for educational purposes at University of Washington Tacoma, TCSS 460 - Software Engineering.
Built for TCSS 460 with focus on teaching professional software engineering practices through hands-on API development.
Questions or Issues?
- Check the Swagger documentation: http://localhost:8000/api-docs
- Review the educational guide: http://localhost:8000/doc
- Examine the comprehensive Postman collection
- See testing strategies in
ai.prof/database-mocking-strategies.md