CROP
ProjectsParts Services

New Service Template - CROP Microservices

Last Updated: 2025-11-19 Purpose: Guide for creating new microservices in CROP monorepo

New Service Template - CROP Microservices

Last Updated: 2025-11-19 Purpose: Guide for creating new microservices in CROP monorepo


Table of Contents

  1. Quick Start
  2. Service Structure
  3. Required Files
  4. Configuration
  5. Best Practices
  6. Checklist

Quick Start

Create New Service (5 minutes)

# 1. Navigate to services directory
cd /Users/vova/Code/CROP/microservices/services

# 2. Create service directory
mkdir my-new-service
cd my-new-service

# 3. Initialize Bun project
bun init -y

# 4. Install dependencies
bun add hono @hono/zod-openapi zod
bun add -d @types/bun

# 5. Create basic structure
mkdir -p src/{routes,middlewares,utils}
touch src/index.ts src/routes/health.ts

Service Structure

services/my-new-service/
├── src/
│   ├── index.ts              # Entry point
│   ├── routes/
│   │   ├── health.ts         # Health check endpoint
│   │   └── api.ts            # API routes
│   ├── middlewares/
│   │   ├── logger.ts         # Request logging
│   │   └── auth.ts           # Authentication
│   ├── utils/
│   │   └── helpers.ts        # Utility functions
│   ├── types/
│   │   └── index.ts          # TypeScript types
│   └── config/
│       └── env.ts            # Environment configuration
├── Dockerfile                # Multi-stage Docker build
├── docker-compose.yml        # Local development
├── cloudbuild.yaml           # GCP Cloud Build config
├── .env.example              # Example environment variables
├── .dockerignore             # Docker ignore patterns
├── package.json              # Dependencies
├── tsconfig.json             # TypeScript configuration
├── CLAUDE.md                 # Service documentation
└── README.md                 # Quick reference

Required Files

1. package.json

{
  "name": "@crop/my-new-service",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "bun run --watch src/index.ts",
    "start": "bun run src/index.ts",
    "build": "bun build src/index.ts --target=bun --outdir=./dist",
    "test": "bun test",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "hono": "^4.x.x",
    "@hono/zod-openapi": "^0.x.x",
    "zod": "^3.x.x"
  },
  "devDependencies": {
    "@types/bun": "latest",
    "bun-types": "latest"
  }
}

2. src/index.ts (Entry Point)

import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { compress } from 'hono/compress'
import healthRoutes from './routes/health'

const app = new Hono()

// Middlewares
app.use('*', logger())
app.use('*', cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['*'],
  credentials: true,
}))
app.use('*', compress())

// Routes
app.route('/health', healthRoutes)
app.route('/api', apiRoutes)

// 404 handler
app.notFound((c) => c.json({ error: 'Not Found' }, 404))

// Error handler
app.onError((err, c) => {
  console.error('Error:', err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

// Start server
const port = Number(process.env.PORT) || 8080
console.log(`🚀 Server starting on port ${port}`)

export default {
  port,
  fetch: app.fetch,
}

3. src/routes/health.ts (Health Checks)

import { Hono } from 'hono'

const health = new Hono()

// Simple health check
health.get('/', (c) => {
  return c.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    service: 'my-new-service',
  })
})

// Deep health check (with dependencies)
health.get('/ready', async (c) => {
  const deep = c.req.query('deep') === 'true'

  if (!deep) {
    return c.json({ ok: true })
  }

  // Check dependencies
  const checks = {
    database: await checkDatabase(),
    cache: await checkCache(),
  }

  const allHealthy = Object.values(checks).every(v => v === 'ok')

  return c.json(
    {
      ok: allHealthy,
      ...checks,
      timestamp: new Date().toISOString(),
    },
    allHealthy ? 200 : 503
  )
})

// Liveness probe
health.get('/live', (c) => {
  return c.json({ alive: true })
})

async function checkDatabase(): Promise<string> {
  try {
    // Add database ping here
    return 'ok'
  } catch (err) {
    return 'error'
  }
}

async function checkCache(): Promise<string> {
  try {
    // Add cache ping here
    return 'ok'
  } catch (err) {
    return 'error'
  }
}

export default health

4. Dockerfile (Multi-stage Build)

# syntax=docker/dockerfile:1.7
# ============================================================================
# Multi-stage Dockerfile for My New Service
# Build from repository root: docker build -f services/my-new-service/Dockerfile .
# ============================================================================

# -------- Stage 1: base
FROM --platform=linux/amd64 oven/bun:1.3.6-debian AS base
WORKDIR /repo
ENV HUSKY=0

# -------- Stage 2: deps-dev (all dependencies)
FROM base AS deps-dev
ENV NODE_ENV=development HUSKY=0

# Copy workspace configuration
COPY bun.lock package.json ./
COPY packages ./packages
COPY services/my-new-service ./services/my-new-service

# Install dependencies
RUN --mount=type=cache,target=/root/.cache/bun \
    HUSKY=0 bun install --ignore-scripts

# -------- Stage 3: deps-prod (production only)
FROM base AS deps-prod
ENV NODE_ENV=production HUSKY=0

COPY bun.lock package.json ./
COPY packages ./packages
COPY services/my-new-service/package.json ./services/my-new-service/

RUN --mount=type=cache,target=/root/.cache/bun \
    HUSKY=0 bun install --production --ignore-scripts

# -------- Stage 4: prod (minimal runtime)
FROM --platform=linux/amd64 oven/bun:1.3.6-debian AS prod

WORKDIR /app

# Copy production dependencies
COPY --from=deps-prod /repo/node_modules ./node_modules
COPY --from=deps-prod /repo/services/my-new-service/node_modules ./services/my-new-service/node_modules

# Copy source code
COPY services/my-new-service ./services/my-new-service

# Non-root user
USER bun

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD ["/usr/local/bin/bun", "run", "services/my-new-service/healthcheck.js"]

# Expose port
EXPOSE 8080

# Start service
CMD ["bun", "run", "services/my-new-service/src/index.ts"]

5. cloudbuild.yaml (CI/CD)

# ============================================================================
# Cloud Build configuration for My New Service
# ============================================================================

steps:
  # Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    env:
      - 'DOCKER_BUILDKIT=1'
    args:
      - 'build'
      - '--build-arg'
      - 'BUILD_TIMESTAMP=${BUILD_ID}'
      - '-f'
      - 'services/my-new-service/Dockerfile'
      - '-t'
      - 'gcr.io/${PROJECT_ID}/my-new-service:${COMMIT_SHA}'
      - '-t'
      - 'gcr.io/${PROJECT_ID}/my-new-service:latest'
      - '.'
    timeout: 900s

  # Push to Container Registry
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'push'
      - '--all-tags'
      - 'gcr.io/${PROJECT_ID}/my-new-service'

  # Deploy to Cloud Run
  - name: 'gcr.io/cloud-builders/gcloud'
    id: 'deploy-to-cloud-run'
    args:
      - 'run'
      - 'deploy'
      - '${_SERVICE_NAME}'
      - '--image=gcr.io/${PROJECT_ID}/my-new-service:${COMMIT_SHA}'
      - '--region=${_REGION}'
      - '--platform=managed'
      - '--allow-unauthenticated'
      - '--quiet'

images:
  - 'gcr.io/${PROJECT_ID}/my-new-service:${COMMIT_SHA}'
  - 'gcr.io/${PROJECT_ID}/my-new-service:latest'

substitutions:
  _SERVICE_NAME: my-new-service
  _REGION: us-east1

timeout: 1800s

6. .env.example

# Server
PORT=8080
NODE_ENV=development

# Database
DATABASE_URL=postgresql://localhost:5432/mydb
DATABASE_POOL_SIZE=10

# External Services
API_KEY=your-api-key-here

# Monitoring
LOG_LEVEL=info

# CORS
ALLOWED_ORIGINS=http://localhost:3000,https://example.com

7. docker-compose.yml (Local Development)

version: '3.8'

services:
  my-new-service:
    build:
      context: ../..
      dockerfile: services/my-new-service/Dockerfile
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/mydb
    volumes:
      - .:/repo/services/my-new-service
    depends_on:
      - db

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

8. .dockerignore

node_modules
dist
.env
.env.*
!.env.example
*.log
.DS_Store
.git
.github
*.md
!README.md
coverage
.vscode
.idea

9. tsconfig.json

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "types": ["bun-types"],
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Configuration

Environment Variables

Required:

  • PORT - Server port (default: 8080)
  • NODE_ENV - Environment (development/production)

Database (if needed):

  • DATABASE_URL - Connection string
  • DATABASE_POOL_SIZE - Connection pool size

Networking (if VPC needed):

  • Configure VPC Access Connector in deployment

Secrets:

  • Use Secret Manager for production
  • Use environment variables for dev/staging

Best Practices

1. Structure

Do:

  • Group by feature (routes, middlewares, utils)
  • Use TypeScript for type safety
  • Follow consistent naming conventions
  • Keep files small and focused

Don't:

  • Put everything in one file
  • Mix concerns (business logic in routes)
  • Skip error handling
  • Hardcode configuration

2. Dependency Management

Do:

  • Use Bun workspaces for shared code
  • Pin major versions in package.json
  • Use monorepo packages/ for shared types
  • Install dependencies explicitly

Don't:

  • Use npm or yarn (use bun only)
  • Install global dependencies
  • Copy-paste code between services

3. Docker

Do:

  • Use multi-stage builds
  • Build from repository root
  • Use .dockerignore
  • Run as non-root user
  • Add health checks

Don't:

  • Build from service directory
  • Include dev dependencies in prod image
  • Use latest tags in production
  • Run as root

4. API Design

Do:

  • Use REST conventions
  • Version your APIs (/api/)
  • Add health endpoints
  • Document with OpenAPI
  • Use standard HTTP status codes

Don't:

  • Mix REST and RPC styles
  • Skip versioning
  • Return 200 for errors
  • Ignore CORS configuration

5. Error Handling

Do:

app.onError((err, c) => {
  console.error('Error:', err)

  if (err instanceof ValidationError) {
    return c.json({ error: err.message }, 400)
  }

  if (err instanceof NotFoundError) {
    return c.json({ error: 'Not found' }, 404)
  }

  return c.json({ error: 'Internal server error' }, 500)
})

Don't:

app.onError((err, c) => {
  // Don't expose internal errors
  return c.json({ error: err.stack }, 500)
})

Deployment

First Deployment

# From repository root
cd /Users/vova/Code/CROP/microservices

# Build image
docker build -f services/my-new-service/Dockerfile -t gcr.io/noted-bliss-466410-q6/my-new-service:v1 .

# Push to registry
docker push gcr.io/noted-bliss-466410-q6/my-new-service:v1

# Deploy to Cloud Run
gcloud run deploy my-new-service \
  --image gcr.io/noted-bliss-466410-q6/my-new-service:v1 \
  --region us-east1 \
  --project noted-bliss-466410-q6 \
  --allow-unauthenticated \
  --execution-environment gen2 \
  --set-env-vars="$(cat services/my-new-service/.env.production | grep -v '^#' | xargs)"

Subsequent Deployments

# Quick deploy
gcloud run deploy my-new-service \
  --source services/my-new-service \
  --region us-east1

# Or use Cloud Build
gcloud builds submit --config services/my-new-service/cloudbuild.yaml

Testing

Local Testing

# Start service
cd services/my-new-service
bun run dev

# Test health endpoint
curl http://localhost:8080/health

# Run tests
bun test

Docker Testing

# Build and run locally
docker-compose up

# Test
curl http://localhost:8080/health

Checklist

New Service Checklist

Setup

  • Created service directory
  • Initialized package.json
  • Installed dependencies
  • Created directory structure

Core Files

  • src/index.ts (entry point)
  • src/routes/health.ts (health checks)
  • Dockerfile (multi-stage)
  • docker-compose.yml (local dev)
  • cloudbuild.yaml (CI/CD)

Configuration

  • .env.example created
  • .dockerignore configured
  • tsconfig.json set up
  • package.json scripts defined

Documentation

  • README.md created
  • CLAUDE.md added (detailed docs)
  • API documented (OpenAPI)
  • Environment variables documented

Testing

  • Health endpoints tested
  • Local Docker build tested
  • Deployed to dev environment
  • Integration tests passing

Deployment

  • Image pushed to GCR
  • Deployed to Cloud Run
  • Environment variables configured
  • VPC configured (if needed)
  • Health checks passing
  • Logs reviewed

Example: Complete New Service

See services/search/ for complete example with:

  • OpenAPI documentation
  • Comprehensive health checks
  • VPC networking
  • Database connections
  • Monitoring endpoints

Next Steps

  1. Create service following this template
  2. Test locally with bun run dev
  3. Test Docker with docker-compose up
  4. Deploy to dev using gcloud run deploy
  5. Monitor logs and metrics
  6. Set up CI/CD (see CI_CD_GUIDE.md)
  7. Add to documentation (ARCHITECTURE.md)

Need Help?

  • Check DEPLOYMENT_GUIDE.md for deployment issues
  • Check services/search/ for real-world example
  • Check ARCHITECTURE.md for infrastructure details

Maintained by: Dev Team Last Review: 2025-11-19

On this page