Use Cases (अपडेट: 2/6/2026)

Claude Code से production API development: OpenAPI, Next.js, Zod और CI

Claude Code से production API बनाएं: OpenAPI contract, Next.js Route Handler, Zod validation, tests और CI.

Claude Code से production API development: OpenAPI, Next.js, Zod और CI

Claude Code से API बनाते समय सबसे बड़ा खतरा यह है कि हम सिर्फ “चलने वाला endpoint” मांग लें। Demo में यह ठीक दिखता है, लेकिन production API को contract, validation, authentication, idempotency, rate limiting, error format, logs, tests और CI चाहिए। इनमें से एक भी हिस्सा छूटे तो बाद में bug, duplicate order या difficult debugging के रूप में खर्च लौटता है।

इस लेख में Claude Code को simple code generator नहीं, बल्कि production API development partner की तरह इस्तेमाल किया गया है। Flow contract-first है: पहले OpenAPI से API का वादा लिखें, फिर Next.js Route Handler बनाएं, Zod से input boundary validate करें, और Vitest plus GitHub Actions से handoff करें।

Masa ने यह pattern छोटे order API पर आजमाया। जब prompt सिर्फ “POST /orders बनाओ” था, तो error response और retry behavior हर बार बदल रहा था। जब prompt में OpenAPI, auth boundary, idempotency rule, error envelope और CI commands जोड़े गए, review बहुत स्पष्ट हो गया।

OpenAPI contract से शुरुआत करें

OpenAPI HTTP API के paths, methods, request body, responses और authentication को machine-readable रूप में लिखता है। आसान भाषा में, यह implementation से पहले API का contract है। Official reference के लिए OpenAPI Specification देखें।

openapi: 3.1.0
info:
  title: Orders API
  version: 1.0.0
paths:
  /api/orders:
    post:
      operationId: createOrder
      summary: Create an order
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 8
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrder"
      responses:
        "201":
          description: Order created
        "400":
          description: Invalid request
        "401":
          description: Missing or invalid token
        "409":
          description: Idempotency key reused with another payload
        "429":
          description: Rate limit exceeded
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    CreateOrder:
      type: object
      required: [customerId, items, currency]
      properties:
        customerId:
          type: string
          minLength: 3
        currency:
          type: string
          enum: [JPY, USD, EUR]
        items:
          type: array
          minItems: 1

Claude Code को prompt में यह बताएं कि contract नहीं टूटना चाहिए:

openapi.yaml को contract मानकर Next.js App Router में POST /api/orders implement करें।
requestBody को Zod से validate करें। Authorization: Bearer <token> को API boundary पर जांचें।
Idempotency-Key required है। Same key और same payload पर same response replay करें।
Same key पर different payload आए तो 409 दें। 60-second rate limit जोड़ें।
हर error { error: { code, message, requestId, details } } में दें।
Vitest tests और GitHub Actions CI workflow भी बनाएं।

Claude Code के लिए official Claude Code overview और Next.js के लिए Route Handlers देखें।

Next.js और Zod से boundary बंद करें

API boundary वह जगह है जहां बाहर से आया data अभी trusted नहीं होता। Browser, mobile app, partner integration और webhook गलत JSON, missing fields, पुराना enum या duplicate request भेज सकते हैं। Zod TypeScript में runtime validation देता है; official docs Zod पर हैं।

यह code app/api/orders/route.ts में copy किया जा सकता है। Local run के लिए इसमें Map है। Production में orders, idempotency और rate limit को database, Redis या API Gateway में ले जाएं।

import { z } from "zod";

export const runtime = "nodejs";

const CreateOrderSchema = z.object({
  customerId: z.string().min(3),
  currency: z.enum(["JPY", "USD", "EUR"]),
  items: z.array(z.object({
    sku: z.string().min(1),
    quantity: z.number().int().positive().max(99),
  })).min(1),
  note: z.string().max(500).optional(),
});

type Order = z.infer<typeof CreateOrderSchema> & {
  id: string;
  status: "accepted";
  createdAt: string;
};

const orders = new Map<string, Order>();
const idempotency = new Map<string, { fingerprint: string; status: number; body: unknown }>();
const buckets = new Map<string, { count: number; resetAt: number }>();

export function __resetForTests() {
  orders.clear();
  idempotency.clear();
  buckets.clear();
}

function send(status: number, body: unknown, headers: Record<string, string> = {}) {
  return Response.json(body, { status, headers });
}

function fail(status: number, code: string, message: string, requestId: string, details?: unknown) {
  return send(status, { error: { code, message, requestId, ...(details ? { details } : {}) } });
}

function actor(req: Request) {
  const expected = process.env.API_TOKEN;
  const raw = req.headers.get("authorization") ?? "";
  const token = raw.startsWith("Bearer ") ? raw.slice(7) : "";
  return expected && token === expected ? token.slice(0, 12) : null;
}

function allowed(key: string) {
  const now = Date.now();
  const current = buckets.get(key);
  if (!current || current.resetAt <= now) {
    buckets.set(key, { count: 1, resetAt: now + 60_000 });
    return true;
  }
  if (current.count >= 30) return false;
  current.count += 1;
  return true;
}

export async function POST(req: Request) {
  const requestId = req.headers.get("x-request-id") ?? crypto.randomUUID();
  const who = actor(req);
  if (!who) return fail(401, "unauthorized", "Invalid API token.", requestId);
  if (!allowed(who)) return fail(429, "rate_limited", "Too many requests.", requestId);

  const idempotencyKey = req.headers.get("idempotency-key");
  if (!idempotencyKey || idempotencyKey.length < 8) {
    return fail(400, "missing_idempotency_key", "Idempotency-Key is required.", requestId);
  }

  const rawBody = await req.text();
  const cacheKey = `${who}:${idempotencyKey}`;
  const cached = idempotency.get(cacheKey);
  if (cached && cached.fingerprint !== rawBody) {
    return fail(409, "idempotency_conflict", "Same key was used with another payload.", requestId);
  }
  if (cached) return send(cached.status, cached.body, { "x-idempotent-replay": "true" });

  let payload: unknown;
  try {
    payload = JSON.parse(rawBody);
  } catch {
    return fail(400, "invalid_json", "Request body must be JSON.", requestId);
  }

  const parsed = CreateOrderSchema.safeParse(payload);
  if (!parsed.success) {
    return fail(400, "validation_failed", "Request does not match the contract.", requestId, parsed.error.flatten());
  }

  const order: Order = { ...parsed.data, id: crypto.randomUUID(), status: "accepted", createdAt: new Date().toISOString() };
  orders.set(order.id, order);

  const body = { data: order, meta: { requestId } };
  idempotency.set(cacheKey, { fingerprint: rawBody, status: 201, body });
  console.info("orders.create", { requestId, orderId: order.id, itemCount: order.items.length });
  return send(201, body, { "x-request-id": requestId });
}

Error envelope, idempotency और observability

Error envelope सभी failure responses का common shape है। इससे frontend या API consumer समझता है कि retry करना है, user को message दिखाना है या support को requestId देना है।

{
  "error": {
    "code": "validation_failed",
    "message": "Request does not match the contract.",
    "requestId": "6f0c9c0f-6db7-4bdf-930b-7cc7d13f3f77",
    "details": {
      "fieldErrors": {
        "items": ["Array must contain at least 1 element(s)"]
      }
    }
  }
}

Idempotency का मतलब है कि same operation retry होने पर दूसरा side effect न बने। Orders, payments, emails, credits और webhooks में यह जरूरी है। Rate limiting infrastructure और cost बचाता है। Observability का सीधा अर्थ है कि बाद में requestId, operation, resource ID, duration और error code से पता चल सके कि क्या हुआ।

तीन से अधिक practical use cases हैं: B2B SaaS order API, internal approval tool, webhook receiver, और public free-tier API. Common pitfalls भी साफ हैं: OpenAPI और Zod mismatch; same idempotency key के साथ different payload accept करना; memory rate limit को multi-instance production में डालना; errors या logs में token, address, card data या personal data लिखना।

Tests और CI को done मानक बनाएं

Tests को बाद के लिए न छोड़ें। Claude Code से implementation के साथ tests भी बनवाएं। यह Vitest server start किए बिना Route Handler call करता है।

import { beforeEach, describe, expect, it } from "vitest";
import { POST, __resetForTests } from "../app/api/orders/route";

const validOrder = {
  customerId: "cus_123",
  currency: "JPY",
  items: [{ sku: "book-001", quantity: 2 }],
};

function req(body: unknown, headers: Record<string, string> = {}) {
  return new Request("http://localhost/api/orders", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      authorization: "Bearer test-token",
      "idempotency-key": crypto.randomUUID(),
      ...headers,
    },
    body: JSON.stringify(body),
  });
}

describe("POST /api/orders", () => {
  beforeEach(() => {
    process.env.API_TOKEN = "test-token";
    __resetForTests();
  });

  it("creates an order", async () => {
    const res = await POST(req(validOrder));
    expect(res.status).toBe(201);
    expect((await res.json()).data.status).toBe("accepted");
  });

  it("rejects invalid input", async () => {
    const res = await POST(req({ ...validOrder, items: [] }));
    expect(res.status).toBe(400);
    expect((await res.json()).error.code).toBe("validation_failed");
  });

  it("returns 409 for conflicting idempotency reuse", async () => {
    const key = "order-key-001";
    await POST(req(validOrder, { "idempotency-key": key }));
    const res = await POST(req({ ...validOrder, currency: "USD" }, { "idempotency-key": key }));
    expect(res.status).toBe(409);
  });
});

CI repository को contract याद दिलाता है। GitHub Actions के लिए official workflow syntax इस्तेमाल करें।

name: api-contract
on:
  pull_request:
    paths:
      - "app/api/**"
      - "tests/**/*.route.test.ts"
      - "openapi.yaml"
jobs:
  api-contract:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx @redocly/cli lint openapi.yaml
      - run: npx vitest run tests/**/*.route.test.ts

CTA और practical result

अगर team backend में Claude Code adopt करना चाहती है, तो Claude Code training और consultation natural next step है। Solo developer पहले free cheatsheet से prompt और checklist आजमा सकता है। Testing details के लिए API test automation और version planning के लिए API versioning strategy पढ़ें।

इस approach को test करने पर output plain endpoint prompt से बेहतर निकला। 409 idempotency conflict, Zod fieldErrors, requestId logs और OpenAPI lint पहले से होने पर Claude Code demo code नहीं, maintainable API starting point देता है।

#Claude Code #API development #OpenAPI #Next.js #Zod #CI
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.