Ekarpena eta garapen errezetak
Monorepo honetako garapen lan ohikoenetarako urratsez urratseko gidak. Errezeta guztiek Arkitektura eta Backend orrietan deskribatutako konbentzioak jarraitzen dituzte.
Fluxu orokorra
- Ekarri
mainadarreko azken aldaketak. - Sortu aldatuko duzun paketera mugatutako adarra (
backend/,frontend/,docs/). - Inplementatu aldaketak beheko konbentzioak jarraituz.
- Exekutatu
tsc --noEmitkaltetutako paketean commit egin aurretik. - Jokabide aldaketarik badago, eguneratu docs-ak
/docs/content/en/barruan.
Errezeta: backend endpoint bat gehitu
Erreferentziazko inplementazioa: backend-js/src/app/api/users/create/.
1 - Karpeta egitura sortu
backend-js/src/app/api/<domain>/<action>/
dtos/
<action>.dto.ts
repository/
<action>.repo.ts
service/
<action>.service.ts
route.ts
types.tsKarpeta izenaren konbentzioa: dtos/ (plurala). Salbuespenik gabe.
2 - Domeinu motak definitu (types.ts)
export interface CreateFooDto {
name: string;
// ... balidatutako field-ak
}
export interface FooRow {
id: number;
name: string;
created_at: string;
}3 - DTO balidatzailea idatzi (dtos/<action>.dto.ts)
import { isString } from "@/app/lib/dto";
import { ValidationError } from "@/app/lib/errors";
import { CreateFooDto } from "../types";
export function validateCreateFooDto(body: unknown): CreateFooDto {
if (!body || typeof body !== "object") {
throw new ValidationError("Invalid request body");
}
const { name } = body as Record<string, unknown>;
if (!isString(name)) throw new ValidationError("name is required");
return { name: name as string };
}Arauak:
- Funtzio izena:
validateXxxDto(body: unknown): XxxDto. - Ez egin SQL deirik DTO barruan.
- Erabili
src/app/lib/dto.ts-eko guard-ak:isString,isEmail,isNumber,isBoolean,isUserRole,isPackageValidStatus. - Input baliogaberako
ValidationErrorbota.
4 - Repository-a idatzi (repository/<action>.repo.ts)
import { connect } from "@/app/config/dbConfig";
import { ResultSetHeader } from "mysql2";
import { FooRow } from "../types";
export async function insertFoo(name: string): Promise<number> {
const db = await connect();
const [result] = await db.execute<ResultSetHeader>(
"INSERT INTO foos (name) VALUES (?)",
[name]
);
return result.insertId;
}
export async function findFooById(id: number): Promise<FooRow | null> {
const db = await connect();
const [rows] = await db.query<(FooRow & import("mysql2").RowDataPacket)[]>(
"SELECT id, name, created_at FROM foos WHERE id = ?",
[id]
);
return rows[0] ?? null;
}Arauak:
- Hemen SQL bakarrik. Ez negozio logikarik.
- Erabili beti prepared statements (
?placeholder-ak). - Ez deitu repository-a zuzenean
route.ts-tik; pasa service-etik.
5 - Service-a idatzi (service/<action>.service.ts)
import { NotFoundError } from "@/app/lib/errors";
import { insertFoo, findFooById } from "../repository/<action>.repo";
import { CreateFooDto, FooRow } from "../types";
export async function createFooService(dto: CreateFooDto): Promise<FooRow> {
const id = await insertFoo(dto.name);
const foo = await findFooById(id);
if (!foo) throw new NotFoundError("Foo not created");
return foo;
}Arauak:
- Negozio logika bakarrik. Ez SQL-rik.
- Bota
src/app/lib/errors.ts-eko erroreak.
6 - Route handler-a idatzi (route.ts)
import { requireAuth } from "@/app/lib/jwt";
import { handleError, res } from "@/app/lib/response";
import { validateCreateFooDto } from "./dtos/<action>.dto";
import { createFooService } from "./service/<action>.service";
import { USER_ROLES } from "@/app/types";
export async function POST(request: Request) {
try {
const auth = requireAuth(request, [USER_ROLES.admin]);
const body = await request.json();
const dto = validateCreateFooDto(body);
const result = await createFooService(dto);
return res.created(result);
} catch (error) {
return handleError("[POST /api/<domain>/<action>]", error);
}
}Arauak:
requireAuth->validateDto->service->res.xxx.- Ez SQL-rik, ez negozio logikarik.
- Pasatu beti endpoint etiketa
handleError-i.
7 - Docs-ak eguneratu
Gehitu endpoint berria docs/content/en/api/index.mdx fitxategian dagokion taldean. Sartu request body eta response shape-a. Egiaztatu gabe dagoena markatu > [!NOTE] Needs verification erabilita.
Errezeta: frontend API dei bat gehitu
Erreferentzia: frontend-app/app/lib/api/auth-api.ts.
1 - Request/response motak gehitu
Sortu edo eguneratu frontend-app/app/utils/types/api/<domain>.types.ts:
export interface CreateFooRequest { name: string }
export interface CreateFooResponse { id: number; name: string; created_at: string }2 - API funtzioa gehitu
frontend-app/app/lib/api/<domain>-api.ts barruan:
import { apiClient } from "./helpers/client";
import type { CreateFooRequest, CreateFooResponse } from "../../utils/types/api/foo.types";
export async function createFoo(payload: CreateFooRequest): Promise<CreateFooResponse> {
const response = await apiClient.post<CreateFooResponse>("/foos/create", payload);
return response.data;
}3 - React Query hook bat gehitu
frontend-app/app/hooks/<domain>/use<Action>.ts barruan:
import { useMutation } from "@tanstack/react-query";
import { createFoo } from "../../lib/api/foo-api";
export function useCreateFoo() {
return useMutation({ mutationFn: createFoo });
}Queries (GET) direnean, gehitu query key bat app/query/keys/ barruan eta options app/query/options/ barruan, dagoen patroia jarraituz.
Errezeta: frontend orri bat gehitu
1 - Page fitxategia sortu
Dagokion App Router taldean frontend-app/app/ barruan:
- Autentikatutako orriak
(main)/(pages)/<page-name>/page.tsxbarruan doaz. - Auth orriak
(auth)/barruan doaz.
2 - Datuak konektatu
Erabili app/hooks/-eko hook existitzen direnak. Behar bada gehitu hook berria (ikusi goian).
3 - Loading egoera gehitu
Gehitu skeleton loader osagai bat (main)/(pages)/<page-name>/components/loaders/<Page>.loader.tsx bidean, orri existitzen direnek erabiltzen duten boneyard-js Skeleton patroia jarraituz.
4 - Alboko nabigaziora gehitu
Orria aside menuan agertu behar bada, gehitu (main)/components/AsideMenu/components/MenuOptions.tsx-eko MenuOptions osagaian.
Errezeta: docs atal berri bat gehitu (EN + ES + EUS)
1 - Eduki fitxategiak sortu
docs/content/en/<section>/index.mdx
docs/content/en/<section>/_meta.ts
docs/content/es/<section>/index.mdx
docs/content/es/<section>/_meta.ts
docs/content/eus/<section>/index.mdx
docs/content/eus/<section>/_meta.ts_meta.ts gutxieneko forma:
export default { index: { title: "Section Title" } };2 - Root _meta.ts-n erregistratu
Gehitu <section>: { title: "..." } docs/content/en/_meta.ts, docs/content/es/_meta.ts eta docs/content/eus/_meta.ts fitxategietan.
3 - Nav item-a gehitu
docs/app/[lang]/layout.tsx barruan, gehitu navItems-era:
{ href: "/<section>", label: nav.<sectionKey> },4 - i18n string-ak gehitu
docs/app/i18n/en.ts barruan, gehitu <sectionKey>: "Section Title" navigation-era bai DocsDictionary interfazean bai en objektuan. Errepikatu es.ts eta eus.ts fitxategietan.
5 - Edukia idatzi
Idatzi ingelesez lehenik. ES eta EUS-rako, idatzi itzulpen osoak edo gehitu laburpena + EN bertsiorako oharra:
> [!NOTE]
> Ingelesezko bertsioa iturri autoritatiboa da. Ikusi [English version](/en/<section>) xehetasun guztietarako.Errezeta: onartutako locale berri bat gehitu
- Sortu
docs/app/i18n/<locale>.ts,DocsDictionaryinterfaze osoarekin bat eginez. - Erregistratu locale-a
docs/app/i18n/index.ts-ekogetDictionaryfuntzioan. - Gehitu
<locale>SUPPORTED_LOCALESzerrendaradocs/middleware.ts-n. - Gehitu
<locale>generateStaticParams-eradocs/app/[lang]/[[...mdxPath]]/page.tsxbarruan erabiltzen bada. - Sortu
docs/content/<locale>/atal guztietako edukiekin.
Docs-ak egiazko mantentzea
- Ez dokumentatu inoiz source code-an egiaztatu ez duzun jokabiderik.
- Erabili
> [!NOTE] Needs verificationziur ez zauden guztirako. - Endpoint bat gehitu ondoren: egiaztatu request/response-a benetako
route.tsetadtos/fitxategien aurka, eta eguneratudocs/content/en/api/index.mdx. - Ingurune aldagaiak aldatzean: eguneratu
docs/content/en/environment/index.mdx. - DB eskema aldatzean: eguneratu
docs/content/en/database/index.mdx.