Skip to content

Create Frontend-Callable Backend Service

Use this guide when creating a new backend service that needs to be accessible by the frontend (publicly exposed via Load Balancer).

Part 1: Implement the Service (alphacode repository)

1. Create the Service Directory Structure

Location: services/<service_name>/

Create the following folder structure:

services/<service_name>/
├── prisma/
│   └── <service_name>/
│       ├── migrations/
│       ├── schema.prisma
│       ├── prisma.config.ts
│       ├── scripts/
│       │   └── index.ts
│       └── seed/
│           └── seed.ts
├── src/
│   ├── app.module.ts
│   ├── main.ts
│   ├── metadata.ts
│   ├── config/
│   │   └── app.config.ts
│   ├── application-layer/
│   │   └── index.ts
│   ├── healthcheck/
│   │   ├── healthcheck.controller.ts
│   │   ├── healthcheck.module.ts
│   │   └── services/
│   │       ├── <service_name>-prisma-health.service.ts
│   │       └── index.ts
│   ├── infrastructure-layer/
│   │   ├── api/
│   │   │   ├── auth/
│   │   │   │   ├── auth.module.ts
│   │   │   │   └── index.ts
│   │   │   ├── context/
│   │   │   │   ├── context.module.ts
│   │   │   │   ├── context.service.ts
│   │   │   │   └── index.ts
│   │   │   ├── controllers/
│   │   │   │   └── index.ts
│   │   │   └── index.ts
│   │   ├── persistence/
│   │   │   ├── prisma/
│   │   │   │   ├── client/
│   │   │   │   │   ├── <service_name>/
│   │   │   │   │   │   ├── <service_name>.prisma.module.ts
│   │   │   │   │   │   ├── <service_name>.prisma.service.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── repositories/
│   │   │   │   │   └── index.ts
│   │   │   │   └── index.ts
│   │   │   └── index.ts
│   │   └── index.ts
│   └── generated/
│       └── swagger/
│           └── <service_name>.swagger.json
├── Dockerfile
├── entrypoint.sh
├── package.json
├── project.json
├── nest-cli.json
├── eslint.config.mjs
├── vitest.config.ts
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.build.json
└── tsconfig.spec.json

2. Configure the Service Entry Point

src/main.ts:

typescript
import { bootstrapNestApp, initTracer } from '@package/nest';
initTracer();

import { AppModule } from './app.module';

const APP_NAME = '<service_name>';

async function bootstrap() {
  await bootstrapNestApp({
    appModule: AppModule,
    appName: APP_NAME,
    isQueryExtended: true,
    hasFrontendClient: true,
  });
}

bootstrap();

3. Configure the App Module

src/app.module.ts:

Import and configure the required modules:

  • ConfigModule with environment validation
  • ContextModule for request context (CLS)
  • LoggingModule for structured logging
  • HealthcheckModule for ALB health checks
  • AuthModule for Supertokens authentication
  • AnalyticModule for PostHog analytics
  • Database module (e.g., <ServiceName>PrismaModule)

4. Configure Environment Validation

src/config/app.config.ts:

Define a class with class-validator decorators for all required environment variables:

  • Database: DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_ADDRESS, DATABASE_PORT, DATABASE_NAME
  • Server: HOST, PORT, FRONTEND_CLIENT_HOST
  • Redis: REDIS_HOST, REDIS_PORT, REDIS_STREAM_CONSUMER_GROUP
  • Supertokens: SUPER_TOKEN_CORE_API_KEY, SUPER_TOKEN_CONNECTION_URI, SUPER_TOKEN_GOOGLE_OAUTH_CLIENT_ID, etc.
  • Logging: LOGGING_APP_NAME, LOGGING_LEVEL, LOGGING_IS_USING_JSON
  • Analytics: POSTHOG_API_KEY, POSTHOG_HOST

5. Set Up Prisma

prisma/<service_name>/schema.prisma:

prisma
generator client {
  provider = "prisma-client-js"
  output   = "../../../node_modules/@prisma/<service_name>-db-client"
}

datasource db {
  provider = "postgresql"
}

// Define your models here

prisma/<service_name>/prisma.config.ts:

typescript
import path from 'node:path';
import { defineConfig, env } from 'prisma/config';

type Env = {
  DATABASE_URL: string;
};

export default defineConfig({
  schema: path.join(__dirname, 'schema.prisma'),
  datasource: {
    url: env<Env>('DATABASE_URL'),
  },
  migrations: {
    path: path.join(__dirname, 'migrations'),
    seed: 'tsx prisma/<service_name>/seed/seed.ts',
  },
});

6. Configure NX Project

project.json:

Include the following targets:

  • clean: Remove generated metadata
  • test: Vitest configuration
  • prisma:generate:<service_name>: Generate Prisma client
  • client:export:nest: Export Swagger client for @package/nest
  • client:export:frontend: Export Swagger client for frontend
  • build: Build with NestJS CLI
  • dev: Development mode with watch

7. Create Dockerfile

dockerfile
FROM node:22

RUN apt-get update && apt-get install -y \
    openssl curl nodejs npm graphicsmagick ghostscript \
  && rm -rf /var/lib/apt/lists/*

RUN npm i -g corepack@latest && corepack enable
RUN npm install -g nx

WORKDIR /app

COPY nx.json package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
COPY packages packages
COPY services/<service_name> services/<service_name>

RUN pnpm install --ignore-scripts --frozen-lockfile --strict-peer-dependencies=false

ARG GITHUB_TOKEN
ENV GITHUB_TOKEN=${GITHUB_TOKEN:-}
RUN nx build <service_name> --skip-nx-cache

EXPOSE <port>

RUN chmod +x /app/services/<service_name>/entrypoint.sh

ENTRYPOINT ["/app/services/<service_name>/entrypoint.sh"]

8. Create Entrypoint Script

entrypoint.sh:

bash
#!/bin/sh
urlencode() {
    echo "$1" | sed 's/:/%3A/g; s/@/%40/g; ...'
}

ENCODED_PASSWORD=$(urlencode "$DATABASE_PASSWORD")
export DATABASE_URL="postgresql://${DATABASE_USERNAME}:${ENCODED_PASSWORD}@${DATABASE_ADDRESS}:${DATABASE_PORT}/${DATABASE_NAME}?schema=public"

echo "🚀 Running Prisma migrations..."
pnpm --filter <service_name> run migrate:deploy:all

echo "🔄 Running scripts on database..."
pnpm --filter <service_name> run prepare:migration

echo "✅ Prisma migrations applied. Starting the app..."
exec node services/<service_name>/dist/main

Part 2: Implement and Deploy the Infrastructure

/infra repository

1. ECR Repository (Global/Tooling)

Location: aws-iac/parallel_tooling/<region>/shared/containers/ecr/<service_name>/terragrunt.hcl

  • Create the ECR repository definition.
  • Grant read/write access to sandbox, staging, and prod accounts.

2. Database (Optional)

Location: aws-iac/parallel_<env>/<region>/<env>/database/<service_name>/

  • db-security-group: Create security group allowing ingress from the service's security group.
  • db: Create the RDS instance configuration.
    • instance_class: Typically db.t4g.micro for staging.
    • snapshot_identifier (Optional): Only use this if you need to restore the database from an existing snapshot (e.g. service-kms). Do not set this for a new, empty database.

3. Service Infrastructure

Location: aws-iac/parallel_<env>/<region>/<env>/containers/services/<service_name>/ Create the following structure:

  • service.hcl: Define local service = "<service_name>".
  • service-security-group/: Security group for the ECS service.
  • service-task-exec-role-policy/: IAM policy for task execution (ECR, Secrets).
  • service-task-role-policy/: IAM policy for the running task (Logs, specialized access).
  • service/: Main ECS service definition.
    • Dependencies: vpc, alb, db (if exists), redis (if exists), s3 (if needed for sampledata/docs).
    • Environment Variables: HOST, PORT, DATABASE_..., REDIS_..., S3_..., FRONTEND_CLIENT_HOST.
    • Secrets: DATABASE_PASSWORD, DD_API_KEY, etc.
    • Resources: Adjust CPU/Memory based on usage (e.g. 256/512 for read-heavy/light services).

4. Redis Access (Optional)

Location: aws-iac/parallel_<env>/<region>/<env>/cache/redis/redis-security-group/terragrunt.hcl

  • Add an ingress rule allowing TCP traffic from <service_name>_service_security_group.

5. Load Balancing (ALB)

Location: aws-iac/parallel_<env>/<region>/<env>/load-balancing/alb/terragrunt.hcl

  • Target Group: Add a new target group key for the service (HTTP, port 80, healthcheck).
  • Listener Rule: Add a listener rule (HTTPS/443).
    • Priority: Ensure unique priority.
    • Condition: Host header <service_name>.<hosted_zone_name>.
    • Action: Forward to the new target group.

Location: aws-iac/parallel_<env>/<region>/<env>/load-balancing/alb-additional-certificates/terragrunt.hcl

  • Certificate: Add a new certificate entry for <service_name>.<hosted_zone_name>.

6. DNS Records

Location: aws-iac/parallel_<env>/us-east-1/<env>/route53/records/terragrunt.hcl

  • Add an A record.
    • Name: <service_name>.${dependency.main_dns_zone.outputs.name}
    • Alias: Point to the ALB DNS name and Zone ID.

7. Logging (Datadog)

Location: aws-iac/parallel_<env>/<region>/<env>/containers/datadog-cloudwatch-log-subscription-filters/terragrunt.hcl

  • Dependency: Add a dependency block for the new service.
  • Filter: Add a new entry in log_subscription_filters pointing to the service's CloudWatch log group.

8. CI/CD (GitHub)

Location: aws-iac/parallel_<env>/<region>/<env>/github/oidc/terragrunt.hcl

  • Ensure the GitHub repository is listed in allowed repositories for OIDC.