AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed Apache-2.0 Self-run

Backend Api

skill-felvieira-claude-skills-fv-03-backend-api · by felvieira

|

No reviews yet
0 installs
30 views
0.0% view→install

Install

$ agentstack add skill-felvieira-claude-skills-fv-03-backend-api

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Dangerous shell/eval execution.

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Backend Api? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Backend Developer - API e Banco de Dados

O Backend define a fundação de dados e lógica de negócio que sustenta toda a aplicação.

Governanca Global

Esta skill segue GLOBAL.md, policies/execution.md, policies/handoffs.md, policies/quality-gates.md, policies/token-efficiency.md, policies/stack-flexibility.md, policies/tool-safety.md e policies/evals.md.

Para exemplos extensos de schema, auth e migracoes, consultar docs/skill-guides/backend-api.md apenas quando necessario.

Quando Usar

  • definir schema, endpoint, auth ou regra de servidor
  • ajustar contrato de API, validacao ou persistencia

Quando Nao Usar

  • para detalhes puramente visuais
  • para documentacao ou review final sem mudanca de backend

Entradas Esperadas

  • spec e regras de negocio
  • dependencias tecnicas e restricoes de seguranca
  • contrato atual ou esperado da API

Saidas Esperadas

  • contrato de API e estrategia de dados consistentes
  • validacoes, auth e erros padronizados
  • handoff claro para Frontend, QA e Security

Responsabilidades

  1. Definir schema do banco de dados
  2. Criar APIs RESTful (ou GraphQL quando justificado)
  3. Implementar autenticação e autorização
  4. Validação server-side de todos os inputs
  5. Tratamento de erros padronizado
  6. Performance: queries otimizadas, caching, indexação

Stack Padrão

Runtime:     Node.js (LTS)
Framework:   Express / NestJS (dependendo da complexidade)
ORM:         Prisma
Banco:       PostgreSQL
Validação:   Zod
Auth:        JWT (access em memoria + refresh em HttpOnly cookie)
Cache:       Redis (quando necessário)
Documentação: OpenAPI/Swagger auto-gerado

Schema do Banco - Convenções

prisma/schema.prisma

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String
  password  String
  role      Role     @default(USER)
  isActive  Boolean  @default(true)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  deletedAt DateTime?

  posts     Post[]
  sessions  Session[]
  
  @@map("users")
  @@index([email])
  @@index([deletedAt])
}

enum Role {
  USER
  ADMIN
  MODERATOR
}

model Session {
  id           String   @id @default(uuid())
  userId       String
  refreshToken String   @unique
  userAgent    String?
  ip           String?
  expiresAt    DateTime
  
  createdAt    DateTime @default(now())
  
  user         User     @relation(fields: [userId], references: [id])
  
  @@map("sessions")
  @@index([userId])
  @@index([expiresAt])
}

Convenções:

  • Nomes de tabela: snake_case plural (via @@map)
  • Nomes de campo: camelCase no Prisma
  • IDs: UUID v4 (nunca auto-increment exposto)
  • Timestamps: sempre createdAt + updatedAt
  • Soft delete: deletedAt nullable
  • Índices: em todo campo usado em WHERE/JOIN/ORDER BY

API - Padrão de Resposta

Toda resposta da API segue este formato:

Sucesso:

{
  "success": true,
  "data": { ... },
  "meta": {
    "page": 1,
    "perPage": 20,
    "total": 100,
    "totalPages": 5
  }
}

Erro:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email inválido",
    "details": [
      {
        "field": "email",
        "message": "Formato de email inválido"
      }
    ]
  }
}

API - Padrão de Endpoints

GET    /api/v1/resources          → Lista (com paginação, filtro, sort)
GET    /api/v1/resources/:id      → Detalhe
POST   /api/v1/resources          → Criar
PATCH  /api/v1/resources/:id      → Atualizar parcial
DELETE /api/v1/resources/:id      → Soft delete

POST   /api/v1/auth/register      → Registro
POST   /api/v1/auth/login         → Login (retorna access token, seta refresh cookie)
POST   /api/v1/auth/refresh       → Refresh token
POST   /api/v1/auth/logout        → Logout (invalida session)
GET    /api/v1/auth/me            → Usuário logado

Query params para listagem:

?page=1&perPage=20           → Paginação
?sort=createdAt&order=desc   → Ordenação
?search=termo                → Busca fulltext
?filter[status]=active       → Filtros
?include=author,comments     → Relations

Autenticação - Fluxo Completo

Login:
1. POST /auth/login { email, password }
2. Valida credenciais
3. Gera access token (JWT, 15min, retornado no response body para uso em memoria)
4. Gera refresh token (UUID, 7d, HttpOnly cookie)
5. Salva session no banco
6. Retorna { accessToken, user }

Refresh:
1. POST /auth/refresh (cookie com refresh token)
2. Valida refresh token no banco
3. Verifica se session não expirou
4. Gera novo access token
5. Opcionalmente rotaciona refresh token
6. Retorna { accessToken, user }

Logout:
1. POST /auth/logout (com access token)
2. Remove session do banco
3. Limpa cookie do refresh token

Validação com Zod - Patterns

src/validators/user.validator.ts

import { z } from 'zod';

const emailSchema = z.string().email('Email inválido').toLowerCase().trim();
const passwordSchema = z.string()
  .min(8, 'Mínimo 8 caracteres')
  .regex(/[A-Z]/, 'Precisa de letra maiúscula')
  .regex(/[0-9]/, 'Precisa de número')
  .regex(/[^A-Za-z0-9]/, 'Precisa de caractere especial');

export const createUserSchema = z.object({
  email: emailSchema,
  password: passwordSchema,
  name: z.string().min(2).max(100).trim(),
});

export const updateUserSchema = createUserSchema.partial().omit({ password: true });

export const loginSchema = z.object({
  email: emailSchema,
  password: z.string().min(1, 'Senha obrigatória'),
});

export const paginationSchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  perPage: z.coerce.number().int().min(1).max(100).default(20),
  sort: z.string().optional(),
  order: z.enum(['asc', 'desc']).default('desc'),
  search: z.string().optional(),
});

export type CreateUserInput = z.infer;
export type PaginationInput = z.infer;

Middleware Pattern

src/middleware/validate.ts

import { ZodSchema } from 'zod';

export const validate = (schema: ZodSchema, source: 'body' | 'query' | 'params' = 'body') => {
  return (req, res, next) => {
    const result = schema.safeParse(req[source]);
    if (!result.success) {
      return res.status(400).json({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Dados inválidos',
          details: result.error.issues.map(i => ({
            field: i.path.join('.'),
            message: i.message,
          })),
        },
      });
    }
    req.validated = result.data;
    next();
  };
};

src/middleware/auth.ts

export const authenticate = async (req, res, next) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED' } });

  try {
    const payload = verifyAccessToken(token);
    req.user = payload;
    next();
  } catch {
    return res.status(401).json({ success: false, error: { code: 'TOKEN_EXPIRED' } });
  }
};

export const authorize = (...roles: Role[]) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ success: false, error: { code: 'FORBIDDEN' } });
    }
    next();
  };
};

src/middleware/errorHandler.ts

export const errorHandler = (err, req, res, next) => {
  console.error(err);

  if (err.code === 'P2002') {
    return res.status(409).json({
      success: false,
      error: { code: 'DUPLICATE', message: 'Registro já existe' },
    });
  }

  res.status(err.status || 500).json({
    success: false,
    error: {
      code: err.code || 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production' ? 'Erro interno' : err.message,
    },
  });
};

Service Pattern - DRY

src/services/base.service.ts

export const createBaseService = (model: any) => ({
  async findMany(params: PaginationInput & { where?: any }) {
    const { page, perPage, sort, order, search, ...filters } = params;
    const skip = (page - 1) * perPage;
    
    const where = { deletedAt: null, ...filters.where };
    
    const [data, total] = await Promise.all([
      model.findMany({
        where,
        skip,
        take: perPage,
        orderBy: sort ? { [sort]: order } : { createdAt: 'desc' },
      }),
      model.count({ where }),
    ]);
    
    return {
      data,
      meta: { page, perPage, total, totalPages: Math.ceil(total / perPage) },
    };
  },
  
  async findById(id: string) {
    return model.findFirst({ where: { id, deletedAt: null } });
  },
  
  async create(data: Partial) {
    return model.create({ data });
  },
  
  async update(id: string, data: Partial) {
    return model.update({ where: { id }, data });
  },
  
  async softDelete(id: string) {
    return model.update({ where: { id }, data: { deletedAt: new Date() } });
  },
});

Stack Alternativa — Plain JS + better-sqlite3

Quando Usar

  • Repos pequenos, scripts, ferramentas CLI, sandboxes e protótipos
  • Projetos onde TypeScript e ORM adicionam mais fricção do que valor
  • Dados locais, single-user, ou leitura intensiva com writes eventuais
  • Casos onde o binário precisa ser zero-dependência (Tauri, Electron, scripts CI)

DB Singleton Pattern

// src/db.js
const Database = require('better-sqlite3');
const path = require('path');

let _db = null;

function getDb() {
  if (_db) return _db;
  _db = new Database(path.join(__dirname, '..', 'data.db'));
  _db.pragma('journal_mode = WAL');
  _db.pragma('foreign_keys = ON');
  _db.pragma('synchronous = NORMAL');
  return _db;
}

module.exports = { getDb };

Schema via db.exec() — sem Prisma

// src/migrate.js
const { getDb } = require('./db');

function migrate() {
  const db = getDb();
  db.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id         TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
      email      TEXT NOT NULL UNIQUE,
      name       TEXT NOT NULL,
      password   TEXT NOT NULL,
      role       TEXT NOT NULL DEFAULT 'user',
      created_at TEXT NOT NULL DEFAULT (datetime('now')),
      deleted_at TEXT
    );
    CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
    CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at);
  `);
}

module.exports = { migrate };

Queries — sempre parametrizadas

// src/repositories/user.repository.js
const { getDb } = require('../db');

const UserRepo = {
  findById(id) {
    return getDb().prepare('SELECT * FROM users WHERE id = ? AND deleted_at IS NULL').get(id);
  },

  findByEmail(email) {
    return getDb().prepare('SELECT * FROM users WHERE email = ? AND deleted_at IS NULL').get(email);
  },

  findAll({ page = 1, perPage = 20 } = {}) {
    const offset = (page - 1) * perPage;
    const rows = getDb()
      .prepare('SELECT * FROM users WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT ? OFFSET ?')
      .all(perPage, offset);
    const { total } = getDb()
      .prepare('SELECT COUNT(*) as total FROM users WHERE deleted_at IS NULL')
      .get();
    return { data: rows, meta: { page, perPage, total, totalPages: Math.ceil(total / perPage) } };
  },

  create(data) {
    const stmt = getDb().prepare(
      'INSERT INTO users (email, name, password, role) VALUES (@email, @name, @password, @role)'
    );
    stmt.run(data);
    return this.findByEmail(data.email);
  },

  softDelete(id) {
    return getDb()
      .prepare("UPDATE users SET deleted_at = datetime('now') WHERE id = ?")
      .run(id);
  },
};

module.exports = UserRepo;

Transactions — síncronas, rápidas

// db.transaction() retorna função que roda tudo atomicamente
const { getDb } = require('./db');

function transferCredits(fromId, toId, amount) {
  const db = getDb();
  const transfer = db.transaction((from, to, amt) => {
    db.prepare('UPDATE wallets SET credits = credits - ? WHERE id = ?').run(amt, from);
    db.prepare('UPDATE wallets SET credits = credits + ? WHERE id = ?').run(amt, to);
  });
  transfer(fromId, toId, amount); // lança se qualquer stmt falhar
}

Quando NÃO Usar

  • Alta concorrência de writes (SQLite é single-writer; use PostgreSQL)
  • Multi-tenant grande ou dados que crescem para GB+
  • Microservices distribuídos que precisam de conexão compartilhada
  • Quando replicação, CDC ou streaming de dados for requisito

Estrategia de Migrations

  • Toda migration DEVE ter UP e DOWN (reversivel)
  • Zero-downtime migration checklist:
  1. Adicionar coluna nova (nullable ou com default)
  2. Deploy codigo que escreve na coluna nova E antiga
  3. Backfill dados antigos
  4. Deploy codigo que le apenas da coluna nova
  5. Remover coluna antiga em migration separada
  • Testar migration em staging ANTES de prod
  • Rollback: se migration falha, executar DOWN imediatamente
  • NUNCA renomear coluna diretamente — criar nova, migrar dados, remover antiga

Evidencia de Conclusao

  • contrato de API coerente com a spec
  • auth, validacao e erros definidos
  • impacto para frontend e QA explicitado

Handoff para Frontend

Entregar:

  1. Documentação OpenAPI/Swagger gerada
  2. Tipos TypeScript exportados (shared types)
  3. Contrato de resposta padronizado
  4. Endpoints de autenticação documentados
  5. Headers necessários (Authorization, CSRF token, etc.)
  6. Rate limits definidos por endpoint
  7. Websocket events se houver real-time
  8. Variáveis de ambiente necessárias no front

Código Limpo

Codigo deve priorizar clareza. Comentarios so fazem sentido quando explicam contexto nao obvio, restricoes externas ou workarounds temporarios.

Integração com Pipeline

  • Orquestrador (skill 09): Coordena quando esta skill é invocada e define a próxima etapa
  • Context Manager (skill 08): Rastreia progresso das tasks dentro desta skill
  • Documentador (skill 10): Documenta entregas desta skill durante o desenvolvimento

Anti-Rationalization

Se você reconhece um desses pensamentos, PARE e siga o processo. Ver policies/anti-rationalization.md.

| Racionalização | Realidade | |---|---| | "Validação no frontend já cobre" | Frontend é bypassável. Backend é a última linha de defesa | | "Trato erros depois" | Erros não tratados viram 500s em produção e logs inúteis | | "É só um endpoint simples" | Endpoints simples sem rate limit, validação e auth são vetores de ataque | | "ORM protege contra SQL injection" | ORM protege queries normais. Raw queries e query builders não | | "Logs são overhead desnecessário" | Logs são a única forma de debugar produção. Sem logs = voo cego |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.