lfx-backend-builder/SKILL.md
Generate compliant backend code for LFX repos — Express.js proxy endpoints (lfx-v2-ui) or Go microservice code (resource services). Encodes the three-file pattern, logging conventions, Goa DSL, NATS messaging, and microservice proxy usage.
npx skillsauth add linuxfoundation/lfx-skills lfx-backend-builderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are generating backend code that must be PR-ready. This skill adapts to the repo type and encodes all backend conventions.
Prerequisites: Upstream API contracts must be validated first (via /lfx-research).
Before generating any code, verify your args include:
| Required | If Missing | |----------|------------| | Specific task (what to build/modify) | Stop and ask — do not guess | | Absolute repo path | Stop and ask — never assume cwd | | File paths to create/modify | Infer from domain, but verify they exist | | Example pattern to follow | Find one yourself (see Read Before Generating) |
If invoked with a FIX: prefix, this is an error correction from the coordinator. Read the error, find the file, apply the targeted fix, and re-validate.
Before writing ANY code, you MUST:
docs/indexer-contract.md (if it exists in the target service repo and the task touches indexing) — authoritative source for data schemas, tags, access control, and parent referencesDo NOT generate code from memory alone. The codebase may have evolved since your training data.
# Example: before modifying committee.service.ts, read it first
cat apps/lfx-one/src/server/services/committee.service.ts
# And read the interface
cat packages/shared/src/interfaces/member.interface.ts
Every new .ts file MUST start with this header:
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT
Every new .go file MUST start with:
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT
When you finish, output a clear summary so the caller (usually /lfx-coordinator) and the user can see what happened:
═══════════════════════════════════════════
/lfx-backend-builder COMPLETE
═══════════════════════════════════════════
Files created:
- (none)
Files modified:
- packages/shared/src/interfaces/member.interface.ts — added bio field
- apps/lfx-one/src/server/services/committee.service.ts — no changes needed (pass-through)
Validation:
- Ran: yarn format (if in Angular repo)
- Result: ✓ passed / ✗ failed with: <error>
Notes:
- Express proxy is pass-through, no field-level changes required
Errors:
- (none)
═══════════════════════════════════════════
Always include the Validation section. Run yarn format after modifying files in the Angular repo, or go vet ./... in Go repos. Report the result.
if [ -f apps/lfx-one/angular.json ] || [ -f turbo.json ]; then
echo "REPO_TYPE=angular" # Express.js proxy layer
elif [ -f go.mod ]; then
echo "REPO_TYPE=go" # Go microservice
fi
The Express.js backend is a thin proxy layer — shapes must match upstream Go microservices.
These references document the Go microservice platform that the Express.js proxy layer connects to. Read the relevant reference when working with upstream APIs.
| Task | Reference |
| --- | --- |
| Repo map, deployment overview, local dev setup, where to start | references/getting-started.md |
| NATS subject naming, service-to-service communication, KV storage | references/nats-messaging.md |
| Goa DSL conventions, make apigen, adding fields, ETag / If-Match pattern | references/goa-patterns.md |
| Indexer message payload, IndexingConfig schema, OpenSearch doc structure | references/indexer-patterns.md |
| OpenFGA tuples, generic fga-sync handlers, permission inheritance, debugging access | references/fga-patterns.md |
| Native vs wrapper service types, which template to follow | references/service-types.md |
| Query service API, how it queries OpenSearch and checks access via fga-sync | references/query-service.md |
| Service Helm chart — deployment, HTTPRoute, Heimdall ruleset, KV buckets, secrets | references/helm-chart.md |
| Building a new resource service end-to-end | references/new-service.md |
Every backend endpoint creates three files in strict order: service -> controller -> route.
src/server/services/<name>.service.ts)The service handles business logic, upstream API calls via MicroserviceProxyService, and response transformation.
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT
import { QueryServiceResponse, MyItem, PaginatedResponse } from '@lfx-one/shared/interfaces';
import { Request } from 'express';
import { logger } from './logger.service';
import { MicroserviceProxyService } from './microservice-proxy.service';
class MyService {
private microserviceProxy: MicroserviceProxyService;
constructor() {
this.microserviceProxy = new MicroserviceProxyService();
}
// READ — query service via /query/resources
public async getItems(req: Request, query: Record<string, any> = {}): Promise<PaginatedResponse<MyItem>> {
logger.debug(req, 'get_items', 'Fetching items from query service', { query });
const { resources, page_token } = await this.microserviceProxy.proxyRequest<QueryServiceResponse<MyItem>>(
req,
'LFX_V2_SERVICE',
'/query/resources',
'GET',
{ ...query, type: 'my_item' }
);
const items = resources.map((r: any) => r.data);
logger.debug(req, 'get_items', 'Fetched items', { count: items.length });
return { data: items, page_token };
}
// WRITE — via /itx/... endpoints
public async createItem(req: Request, payload: Partial<MyItem>): Promise<MyItem> {
logger.debug(req, 'create_item', 'Creating item', { payload });
const result = await this.microserviceProxy.proxyRequest<MyItem>(req, 'LFX_V2_SERVICE', '/itx/items', 'POST', payload);
return result;
}
}
export const myService = new MyService();
Service rules:
MicroserviceProxyService for ALL external API calls (no raw fetch/axios)/query/resources with type parameter/itx/... endpoints matching upstream service pathslogger.debug() for step-by-step tracinglogger.info() for significant operations (V1->V2 transforms, enrichment)logger.warning() for recoverable errors (returning null/empty)serverLogger directly — always use logger from logger.servicesrc/server/controllers/<name>.controller.ts)The controller is the HTTP boundary — validation, logging lifecycle, and response.
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT
import { NextFunction, Request, Response } from 'express';
import { validateUidParameter } from '../helpers/validation.helper';
import { logger } from '../services/logger.service';
import { myService } from '../services/my.service';
export const getItems = async (req: Request, res: Response, next: NextFunction) => {
const startTime = logger.startOperation(req, 'get_items', {});
try {
const items = await myService.getItems(req, req.query);
logger.success(req, 'get_items', startTime, { count: items.data.length });
return res.json(items);
} catch (error) {
logger.error(req, 'get_items', startTime, error, {});
return next(error);
}
};
Controller rules:
logger.startOperation() -> try/catch -> logger.success() or logger.error() + next(error)res.status(500).json() — always next(error) for centralized error handlingsnake_casevalidateUidParameter from helpers for parameter validationstartOperation per HTTP endpoint — never duplicate in servicessrc/server/routes/<name>.route.ts)// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT
import { Router } from 'express';
import { getItems, getItemById, createItem } from '../controllers/my.controller';
const router = Router();
router.get('/', getItems);
router.get('/:uid', getItemById);
router.post('/', createItem);
export default router;
Route registration: The route must be registered in server.ts, which is a protected file. Tell the contributor:
"The route file is created, but it needs to be registered in
server.ts. Since that's a protected infrastructure file, please ask a code owner to add the route registration."
Follow the logging patterns rule for full details. Summary:
| Layer | Pattern | When |
|-------|---------|------|
| Controller | logger.startOperation/success/error with startTime | Every HTTP endpoint |
| Service | logger.debug() | Step-by-step tracing |
| Service | logger.info() | Significant ops (transforms, enrichment) |
| Service | logger.warning() | Recoverable errors (returning null/empty) |
| Infrastructure | Pass undefined for req | Startup, NATS, Snowflake |
MicroserviceError.fromMicroserviceResponse() for upstream failuresServiceValidationError.forField() for input validationres.status(500).json() — always next(error)Default: Use the user's bearer token (req.bearerToken). M2M tokens only for public endpoints or privileged upstream calls after user authorization is verified.
PaginatedResponse<T> with page_tokenfetchAllQueryResources helperpage_size (not limit), conditional page_token spread| Anti-Pattern | Correct Pattern |
|-------------|-----------------|
| import { serverLogger } | import { logger } from './logger.service' |
| res.status(500).json({ error }) | next(error) |
| fetch('http://...') or axios.get(...) | this.microserviceProxy.proxyRequest(...) |
| const x = req.body.field \|\| '' | Use validation helpers |
| Hardcoded upstream URLs | Environment variable via MicroserviceProxyService |
| console.log(...) | logger.debug(req, ...) |
MicroserviceProxyService (not raw fetch/axios)logger service (not serverLogger)logger.startOperation() / logger.success() / logger.error()next(error) (not res.status(500))snake_casepage_size (not limit)For Go microservice repos (lfx-v2-*-service), follow the platform conventions.
cmd/{service}-api/
├── design/ ← Goa DSL (API contract)
│ ├── {service}.go ← API, Service, Method definitions
│ └── type.go ← Type definitions, attribute functions
├── service/ ← API handler implementation
└── main.go
internal/
├── domain/
│ ├── model/ ← Domain structs with Tags() method
│ └── port/ ← Reader/writer/publisher interfaces
├── infrastructure/
│ └── nats/
│ ├── client.go ← NATS connection + KV bucket init
│ ├── storage.go ← KV CRUD with optimistic locking
│ └── messaging_publish.go ← Index + access message publishing
└── service/
├── {resource}_writer.go ← Orchestrates writes
└── {resource}_reader.go ← Read operations
gen/ ← GENERATED — never edit (make apigen)
charts/ ← Helm chart for deployment
Goa API Design: Define endpoints in cmd/{service}/design/. Run make apigen to regenerate gen/. See references/goa-patterns.md.
NATS Messaging: Publish index + access messages on every write. See references/nats-messaging.md and references/indexer-patterns.md.
FGA Access Control: Use generic fga-sync handlers for access tuples. See references/fga-patterns.md.
Service Types: Native services own data in NATS KV. Wrapper services proxy to external systems. See references/service-types.md.
Helm Chart: One rule per Goa endpoint in ruleset.yaml. See references/helm-chart.md.
| Anti-Pattern | Correct Pattern |
|-------------|-----------------|
| Editing files in gen/ | Run make apigen to regenerate |
| Hardcoding NATS subjects | Use subject constants from shared package |
| Missing Tags() on domain model | Always implement — used for OpenSearch indexing |
| Missing index message on writes | Publish on every create, update, delete |
| HTTP calls between services | Use NATS request/reply |
.go filestype.go + {service}.go)make apigen runs cleanlyTags() methoddocs/indexer-contract.md (if present) updated if indexing behavior changed (same PR)/livez, /readyz) implementedThis skill DOES:
packages/shared/This skill does NOT:
/lfx-ui-builder)/lfx-product-architect)server.ts, middleware, build config) — flag for code ownerdevelopment
LFX cross-repo topology and ownership router. Use when the task spans more than one LFX repo, asks "which repo owns X", "where does Y live", "what repos does this touch", "what consumes Z", or needs a peer-repo file path from inside a single repo. Loads per-repo configs when invoked from the LFX workspace root with a full task prompt; gives targeted cross-repo guidance when invoked from inside a single repo. Also answers LFX glossary and topology questions, and performs read-only discovery when the user asks whether a contract, API, event, field, workflow, or repo capability exists. Do not fire for single-repo implementation tasks where the active repo's own CLAUDE.md already governs (those belong to the repo's local skills). Do not fire for V2 platform composition, service classes, or cross-service handoffs (use `/lfx-skills:lfx-platform-architecture`), ITX wrapper plumbing (`/lfx-skills:lfx-itx-integration`), or Intercom app/Fin workflows (`/lfx-skills:lfx-intercom`).
tools
Create a new ticket in the LFXV2 Jira project (linuxfoundation.atlassian.net). Guides the user through picking an issue type (Bug, Story, Task, Epic), writing a concise summary, and capturing the requirement, feature, or bug context, collecting reproduction steps for bugs. Optionally attaches a parent epic, labels, or priority if the user provides them. Submits the ticket via Atlassian MCP and returns the URL. Use this skill any time someone asks to "create a Jira ticket", "open an LFXV2 ticket", "file a bug", "log a story", "write up a feature request", "draft a ticket", or any variation of submitting work into LFXV2.
testing
Combine multiple feature branches across repos into worktrees for end-to-end journey testing. Create, refresh, and teardown integration environments that merge branches from multiple repos.
devops
Guide users through requesting Snowflake access at the Linux Foundation. Handles two request types: (1) individual user access, adding or modifying an entry in users.tf in the lfx-snowflake-terraform repo, and (2) service account creation, adding an entry in service_accounts.tf. For each, the skill collects the necessary details, generates the exact Terraform HCL block to add, explains where to place it, and guides the user through the PR process. Use this skill any time someone asks about Snowflake access, permissions, user provisioning, service accounts, or making changes to the lfx-snowflake-terraform repo, including phrases like "get access to Snowflake", "add me to Snowflake", "need a service account", "request Snowflake permissions", "I need to query Snowflake", or "how do I get Snowflake access".