Claude Code Plugins

Community-maintained marketplace

Feedback

backend-developer

@ncrmro/tetrastack
1
0

Expert in building backend features with Next.js server actions, Drizzle ORM models, database operations, and the many-first design pattern. Use when creating or modifying server actions, database models, schemas, migrations, or backend business logic.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name backend-developer
description Expert in building backend features with Next.js server actions, Drizzle ORM models, database operations, and the many-first design pattern. Use when creating or modifying server actions, database models, schemas, migrations, or backend business logic.
allowed-tools Read, Edit, Write, Grep, Glob, Bash, mcp__typescript-lsp__definition, mcp__typescript-lsp__references, mcp__typescript-lsp__diagnostics, mcp__typescript-lsp__edit_file, mcp__typescript-lsp__rename_symbol

Backend Developer Skill

Expert backend development using Next.js server actions, Drizzle ORM, and the many-first design pattern.

Core Principles

1. Many-First Design Pattern

CRITICAL: All CRUD operations use arrays by default. Never create single-record wrappers.

Correct:

// In actions or models
const [tag] = await insertTags([data]);
const [updated] = await updateTags([eq(tags.id, id)], data);
await deleteTags([eq(tags.id, id)]);

Wrong:

// NEVER create these
export async function createTag(data) { ... }
export async function updateTag(id, data) { ... }
export async function deleteTag(id) { ... }

2. Server Actions Over API Routes

Always prefer server actions (src/actions/) over API routes unless specifically needed for webhooks or external integrations.

IMPORTANT: Never use redirect() or permanentRedirect() in server actions as they throw errors. Return success/error objects instead and handle navigation in client components.

Correct:

'use server';

export async function createItem(data: InsertItem) {
  try {
    const [item] = await insertItems([data]);
    return { success: true, item };
  } catch (error) {
    return { success: false, error: 'Failed to create item' };
  }
}

Wrong:

'use server';

export async function createItem(data: InsertItem) {
  const [item] = await insertItems([data]);
  redirect('/items'); // DO NOT USE - throws error
}

3. Model Factory Pattern

Use createModelFactory from src/lib/models/ for all database operations:

import { createModelFactory } from '@/lib/models';

const {
  insert,
  select,
  update,
  delete: deleteOp,
  buildConditions,
  takeFirst,
} = createModelFactory(db, tableName, tableSchema);

Directory Structure & Responsibilities

src/actions/

Server actions for data mutations and form handling. Always read src/actions/README.md before working in this area.

Pattern:

'use server';

import { insertItems } from '@/models/items';

export async function createItem(formData: FormData) {
  const data = {
    /* validate and parse */
  };
  const [item] = await insertItems([data]);
  return { success: true, item };
}

src/models/

Database operations and business logic. Always read src/models/README.md before working in this area.

Pattern:

import { createModelFactory } from '@/lib/models';
import { db } from '@/lib/db';
import { items } from '@/lib/db/schema.items';

const {
  insert,
  select,
  update,
  delete: deleteOp,
} = createModelFactory(db, 'items', items);

export const insertItems = insert;
export const selectItems = select;
export const updateItems = update;
export const deleteItems = deleteOp;

src/lib/db/

Database schema and connection. Always read src/lib/db/README.md before working with schemas or migrations.

Key files:

  • schema.*.ts - Eight domain-specific schema files
  • index.ts - Database connection and table exports
  • Migration commands in Makefile

Database Operations

Drizzle Query Patterns

Select with conditions:

import { eq, and, gte } from 'drizzle-orm';

const items = await selectItems([
  eq(items.userId, userId),
  gte(items.createdAt, startDate),
]);

Select one (takeFirst):

const { takeFirst } = createModelFactory(db, 'items', items);
const item = await takeFirst([eq(items.id, itemId)]);

Update with conditions:

const [updated] = await updateItems([eq(items.id, itemId)], {
  name: 'New Name',
});

Transactions:

await db.transaction(async (tx) => {
  const [item] = await tx.insert(items).values(data).returning();
  await tx.insert(itemRelations).values({ itemId: item.id });
});

Migrations

Important: Use make migration-reconcile to resolve migration conflicts.

Generate migration:

make migration-generate name=add_new_field

Run migrations:

make migration-migrate

TypeScript Best Practices

Use TypeScript LSP Tools

Prefer TypeScript LSP MCP tools for TypeScript-specific operations:

  • mcp__typescript-lsp__edit_file instead of Edit for TypeScript files
  • mcp__typescript-lsp__references instead of Grep for finding usages
  • mcp__typescript-lsp__definition for navigating to definitions
  • mcp__typescript-lsp__rename_symbol for safe refactoring
  • mcp__typescript-lsp__diagnostics to check type errors

Type Safety

// Use Drizzle's type inference
type InsertItem = typeof items.$inferInsert;
type SelectItem = typeof items.$inferSelect;

// Use Zod for validation
import { z } from 'zod';

const itemSchema = z.object({
  name: z.string().min(1),
  quantity: z.number().positive(),
});

Avoid Dynamic Imports

Never use dynamic imports. Always use top-level static imports:

Correct:

import { something } from './module';

Wrong:

const module = await import('./module');

Testing

Always use test factories from tests/factories/. Read tests/factories/README.md for patterns.

Best practice - Use minimal custom parameters:

Good:

await foodFactory.create();
await foodFactory.processed().fat().create();

Avoid:

await foodFactory.create({
  name: 'X',
  type: 'Y',
  category: 'Z',
});

Docker Commands

All npm commands must run inside Docker:

docker compose exec web npm run test:unit
docker compose exec web npm install package-name

Or use Makefile shortcuts:

make ci        # Run all tests and linting
make format    # Format code

Common Tasks

Create a new model

  1. Define schema in appropriate src/lib/db/schema.*.ts file
  2. Generate migration: make migration-generate name=add_model_name
  3. Create model file in src/models/model-name.ts using createModelFactory
  4. Create factory in tests/factories/model-name.factory.ts
  5. Write tests in tests/unit/models/model-name.test.ts

Create a server action

  1. Create file in src/actions/feature-name.ts
  2. Add "use server" directive at top
  3. Import model operations from src/models/
  4. Return success/error objects (never use redirect())
  5. Write integration tests in tests/integration/actions/

Add a database field

  1. Update schema in src/lib/db/schema.*.ts
  2. Generate migration: make migration-generate name=add_field_name
  3. Review and run migration: make migration-migrate
  4. Update TypeScript types (auto-inferred from schema)
  5. Update factories if needed

Reference Documentation

Always consult these READMEs when working in their areas:

Troubleshooting

Type errors after schema change

docker compose exec web npm run typecheck

Migration conflicts

make migration-reconcile

Test failures

docker compose exec web npm run test:unit -- --reporter=verbose