Welcome Contributors

Thank you for your interest in contributing to the SG Cars Trends API! This guide will help you get started and ensure your contributions align with our project standards.

Getting Started

1. Fork and Clone

# Fork the repository
gh repo fork sgcarstrends/backend

# Clone your fork
gh repo clone YOUR_USERNAME/backend
cd backend

2. Set Up Development Environment

Follow the Development Setup guide to configure your local environment.

3. Create a Feature Branch

# Create and switch to a new branch
git checkout -b feature/your-feature-name

# Or for bug fixes
git checkout -b fix/issue-description

Development Workflow

Branch Naming Convention

Use descriptive branch names with appropriate prefixes:
PrefixPurposeExample
feature/New featuresfeature/add-coe-analytics
fix/Bug fixesfix/authentication-error
docs/Documentation updatesdocs/update-api-reference
refactor/Code refactoringrefactor/optimize-queries
test/Test improvementstest/add-integration-tests
chore/Maintenance taskschore/update-dependencies

Commit Message Format

Follow the Conventional Commits specification:
# Feature
git commit -m "feat: add COE comparison analytics endpoint"

# Bug fix
git commit -m "fix: resolve authentication token validation issue"

# Documentation
git commit -m "docs: update API authentication guide"

# Breaking change
git commit -m "feat!: change API response format for consistency"

# With scope
git commit -m "feat(api): add pagination to cars endpoint"

Commit Message Structure

type(scope): description

[optional body]

[optional footer]
Types:
  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, etc.)
  • refactor: Code refactoring
  • test: Adding or updating tests
  • chore: Maintenance tasks

Code Guidelines

TypeScript Standards

Type Safety

  • Use strict TypeScript configuration
  • Avoid any type (use unknown instead)
  • Define explicit return types for functions
  • Use type guards for runtime validation

Code Style

  • Use Biome for formatting and linting
  • Double quotes for strings
  • 2-space indentation
  • Trailing commas in multi-line objects

Code Examples

// ✅ Good: Explicit types and error handling
export async function getCarsByMonth(
  month: string
): Promise<Car[]> {
  try {
    const result = await db
      .select()
      .from(cars)
      .where(eq(cars.month, month));
    
    return result;
  } catch (error) {
    logger.error('Failed to fetch cars', { month, error });
    throw new Error(`Failed to fetch cars for month ${month}`);
  }
}

// ✅ Good: Type guards
function isValidFuelType(fuelType: unknown): fuelType is FuelType {
  return typeof fuelType === 'string' && 
    ['Petrol', 'Diesel', 'Electric', 'Hybrid', 'Others'].includes(fuelType);
}

// ✅ Good: Proper error types
class ValidationError extends Error {
  constructor(message: string, public field: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

API Development Guidelines

Endpoint Design

  • Use RESTful conventions
  • Consistent response formats
  • Proper HTTP status codes
  • Clear error messages

Validation

  • Validate all input parameters
  • Use Zod schemas for validation
  • Sanitize user inputs
  • Return meaningful error messages

Database Guidelines

// ✅ Good: Efficient query with proper types
export async function getCarsByMakeAndMonth(
  make: string,
  month: string
): Promise<Car[]> {
  return await db
    .select()
    .from(cars)
    .where(and(
      eq(cars.make, make),
      eq(cars.month, month)
    ))
    .orderBy(desc(cars.number));
}

// ✅ Good: Proper transaction handling
export async function updateCarData(
  carUpdates: CarUpdate[]
): Promise<void> {
  await db.transaction(async (tx) => {
    for (const update of carUpdates) {
      await tx
        .update(cars)
        .set(update)
        .where(eq(cars.id, update.id));
    }
  });
}

// ✅ Good: Error handling with database operations
export async function insertCarBatch(
  carData: NewCar[]
): Promise<Car[]> {
  try {
    return await db
      .insert(cars)
      .values(carData)
      .returning();
  } catch (error) {
    if (error instanceof Error && error.message.includes('duplicate key')) {
      throw new ValidationError('Duplicate car data', 'month,make');
    }
    throw error;
  }
}

Testing Requirements

Test Coverage

All contributions must include appropriate tests:

Unit Tests

  • Test individual functions
  • Mock external dependencies
  • Cover edge cases and error scenarios
  • Aim for 80%+ coverage

Integration Tests

  • Test API endpoints
  • Test database interactions
  • Test workflow processes
  • Verify error handling

Writing Tests

// Unit test example
describe('calculateGrowthRate', () => {
  it('should calculate positive growth rate', () => {
    expect(calculateGrowthRate(120, 100)).toBe(20);
  });

  it('should calculate negative growth rate', () => {
    expect(calculateGrowthRate(80, 100)).toBe(-20);
  });

  it('should handle zero previous value', () => {
    expect(calculateGrowthRate(100, 0)).toBeNull();
  });

  it('should handle equal values', () => {
    expect(calculateGrowthRate(100, 100)).toBe(0);
  });
});

// Integration test example
describe('Cars API', () => {
  beforeEach(async () => {
    await setupTestDatabase();
  });

  afterEach(async () => {
    await cleanupTestDatabase();
  });

  it('should return cars for valid month', async () => {
    const response = await testClient.get('/v1/cars?month=2024-01');
    
    expect(response.status).toBe(200);
    expect(response.body.success).toBe(true);
    expect(response.body.data).toBeInstanceOf(Array);
  });
});

Test Commands

# Run all tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:coverage

# Run specific test file
pnpm test -- src/utils/__tests__/format-percentage.test.ts

Documentation

Code Documentation

/**
 * Calculates the growth rate between two values
 * @param current - The current value
 * @param previous - The previous value
 * @returns The growth rate as a percentage, or null if previous is 0
 * @example
 * ```typescript
 * const growth = calculateGrowthRate(120, 100); // Returns 20
 * ```
 */
export function calculateGrowthRate(
  current: number, 
  previous: number
): number | null {
  if (previous === 0) return null;
  return ((current - previous) / previous) * 100;
}

/**
 * Retrieves car registration data for a specific month
 * @param month - Month in YYYY-MM format
 * @throws {ValidationError} When month format is invalid
 * @throws {Error} When database query fails
 */
export async function getCarsByMonth(month: string): Promise<Car[]> {
  // Implementation
}

API Documentation

When adding new endpoints, update the relevant documentation:
  1. OpenAPI Schema: Update Zod schemas and route definitions
  2. API Reference: Add endpoint documentation to /docs/api-reference/
  3. Examples: Provide usage examples in /docs/examples/
  4. Guides: Update relevant guides if functionality changes

Pull Request Process

1. Before Submitting

1

Run Tests

Ensure all tests pass locally
pnpm test
2

Run Linting

Fix any linting issues
pnpm lint
3

Update Documentation

Update relevant documentation and examples
4

Add Changeset

Add a changeset for your changes
pnpm changeset

2. Pull Request Template

Use this template for your pull requests:
## Summary
Brief description of the changes made

## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that causes existing functionality to change)
- [ ] Documentation update
- [ ] Refactoring (no functional changes)
- [ ] Performance improvement

## Changes Made
- List specific changes
- Include any new endpoints or modified behavior
- Mention any breaking changes

## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed
- [ ] All tests pass locally

## Documentation
- [ ] Code comments added/updated
- [ ] API documentation updated
- [ ] Examples updated
- [ ] Changelog entry added

## Related Issues
Fixes #issue_number
Relates to #issue_number

## Screenshots (if applicable)
Include screenshots for UI changes or API responses

## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Tests added for new functionality
- [ ] Documentation updated
- [ ] No breaking changes (or properly documented)

3. Review Process

Automated Checks

  • Tests must pass
  • Linting must pass
  • TypeScript compilation must succeed
  • No security vulnerabilities

Manual Review

  • Code quality and readability
  • Test coverage and quality
  • Documentation completeness
  • Breaking change assessment

Issue Reporting

Bug Reports

When reporting bugs, include:
## Bug Description
Clear description of what the bug is

## Steps to Reproduce
1. Step one
2. Step two
3. Step three

## Expected Behavior
What you expected to happen

## Actual Behavior
What actually happened

## Environment
- OS: [e.g., macOS 14.0]
- Node.js version: [e.g., 18.17.0]
- API version: [e.g., 1.6.0]

## Error Messages

Feature Requests

## Feature Description
Clear description of the proposed feature

## Use Case
Explain why this feature would be useful

## Proposed Solution
Describe how you think this should work

## Alternatives Considered
Any alternative solutions you've considered

## Additional Context
Any other relevant information or examples

Community Guidelines

Code of Conduct

Be Respectful

  • Treat all contributors with respect
  • Be constructive in feedback
  • Welcome newcomers
  • Help others learn and grow

Be Professional

  • Use professional language
  • Focus on technical merits
  • Be patient with questions
  • Provide helpful feedback

Communication Channels

GitHub Issues

For bug reports, feature requests, and technical discussions

GitHub Discussions

For general questions, ideas, and community discussions

Development Tools

{
  "recommendations": [
    "biomejs.biome",
    "ms-vscode.vscode-typescript-next",
    "bradlc.vscode-tailwindcss",
    "ms-vscode.vscode-json",
    "redhat.vscode-yaml",
    "ms-vscode.vscode-eslint"
  ]
}

Git Hooks

Set up pre-commit hooks for code quality:
# Install husky for git hooks
pnpm add -D husky

# Set up pre-commit hook
echo "pnpm lint && pnpm test" > .husky/pre-commit
chmod +x .husky/pre-commit

Release Process

Changesets

Use changesets to manage releases:
# Add a changeset
pnpm changeset

# Version packages
pnpm changeset version

# Create release tags
pnpm changeset tag

Semantic Versioning

We follow Semantic Versioning:
Change TypeVersion BumpExample
Breaking changesMajor1.0.0 → 2.0.0
New featuresMinor1.0.0 → 1.1.0
Bug fixesPatch1.0.0 → 1.0.1

Getting Help

Documentation

Support

GitHub Issues

Report bugs or request features

GitHub Discussions

Ask questions and get help from the community

Recognition

Contributors are recognized in:
  • GitHub Contributors: Automatic recognition on the repository
  • Changelog: Major contributions mentioned in release notes
  • Documentation: Contributors section in the main README
Thank you for contributing to the SG Cars Trends API! 🚗📊