---
name: 'API'
root: './'
output: '**/*'
ignore: []
questions:
  module: 'Enter module name(singular and kebab like item-category)'
  tag: 'Enter tag name such as user'
  isIdNumber:
    confirm: 'Is type of id number? (if cuid, then no)'
  isAuthRequired:
    confirm: 'Is auth required api?'
---

# `src/app/api/{{ inputs.module | plur }}/route.ts`

```ts
{{- module := inputs.module -}}
{{- modulep := inputs.module | pascal -}}

import { NextResponse, type NextRequest } from 'next/server';
import { prisma } from '@/lib/Prisma';
import { getPrismaFindManyQuery } from '@/lib/BuildPrismaQuery';
import { handlePrismaError } from '@/lib/PrismaErrorHandler';
import { handleZodError } from '@/lib/ZodErrorHandler';
import { ERROR_MAP } from '@/lib/ErrorMessages';
import {
  {{ modulep }}CreateInputSchema as CreateInputSchema,
  {{ modulep }}FindManyArgsSchema as FindManyArgsSchema,
  type {{ modulep }} as RequestType,
} from '@/schemas/zod';
import {
  {{ modulep }}PrismaInclude as PrismaInclude,
  {{ modulep }}PrismaSelect as PrismaSelect,
  format{{ modulep }}Params,
} from '@/schemas/config';

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const findQuery = await getPrismaFindManyQuery(
    PrismaSelect,
    PrismaInclude,
    searchParams
  );

  // validate query
  const query = FindManyArgsSchema.safeParse(findQuery);
  if (query.success === false) {
    const { errorCode, errorObject } = handleZodError(query.error);
    return NextResponse.json({ error: errorObject }, { status: errorCode });
  }

  let statusCode = 200;
  const res = await prisma.{{ module }}
    .findMany(query.data)
    .then((res) => {
      if (!res) {
        statusCode = 404;
        return { error: ERROR_MAP[404] };
      } else {
        return res;
      }
    })
    .catch((err) => {
      const { errorCode, errorObject } = handlePrismaError(err);
      statusCode = errorCode;
      return { error: errorObject };
    });
  const totalCount = await prisma.{{ module }}.aggregate({ _count: true });

  return NextResponse.json(res, {
    headers: {
      'x-total-count': String(totalCount._count),
    },
    status: statusCode,
  });
}

export async function POST(request: Request) {
  const requestBody: Partial<RequestType> = await request
    .json()
    .catch(() => {});

  const params = format{{ modulep }}Params(requestBody);
  const query = CreateInputSchema.safeParse(params);

  if (query.success === false) {
    const { errorCode, errorObject } = handleZodError(query.error);
    return NextResponse.json({ error: errorObject }, { status: errorCode });
  }

  let statusCode = 200;

  const res = await prisma.{{ module }}
    .create({
      data: query.data,
    })
    .catch((err) => {
      const { errorCode, errorObject } = handlePrismaError(err);
      statusCode = errorCode;
      return { error: errorObject };
    });

  return NextResponse.json(res, { status: statusCode });
}

```

# `src/app/api/{{ inputs.module | plur }}/[id]/route.ts`

```ts
{{- module := inputs.module -}}
{{- modulep := inputs.module | pascal -}}

import { NextResponse } from 'next/server';
import { getPrismaFindUniqueQuery } from '@/lib/BuildPrismaQuery';
import { ERROR_MAP } from '@/lib/ErrorMessages';
import { prisma } from '@/lib/Prisma';
import { handlePrismaError } from '@/lib/PrismaErrorHandler';
import { validateSlugParameters } from '@/lib/SchemaValidator';
import { handleZodError } from '@/lib/ZodErrorHandler';
import {
  {{ modulep }}FindUniqueArgsSchema as FindUniqueArgsSchema,
  {{ modulep }}UpdateInputSchema as UpdateInputSchema,
  {{ modulep }}WhereUniqueInputSchema as WhereUniqueInputSchema,
  type {{ modulep }} as RequestType,
} from '@/schemas/zod';
import {
  {{ modulep }}PrismaInclude as PrismaInclude,
  {{ modulep }}PrismaSelect as PrismaSelect,
  format{{ modulep }}Params,
} from '@/schemas/config';

type Props = {
  params: {
    id: string;
  };
};

async function _validateSlugParameters(id: string) {
  return await validateSlugParameters(WhereUniqueInputSchema, {{ if inputs.isIdNumber }}+{{ end }}id);
}

export async function GET(request: Request, { params: { id } }: Props) {
  let findQuery = await getPrismaFindUniqueQuery(PrismaSelect, PrismaInclude);
  const { success, error, where } = await _validateSlugParameters(id);
  if (success === false) {
    return NextResponse.json({ error }, { status: 400 });
  } else {
    findQuery = { ...findQuery, where };
  }

  // validate query
  const query = FindUniqueArgsSchema.safeParse(findQuery);
  if (query.success === false) {
    const { errorCode, errorObject } = handleZodError(query.error);
    return NextResponse.json(
      { ...ERROR_MAP[errorCode], errors: errorObject },
      { status: errorCode }
    );
  }

  let statusCode = 200;
  const res = await prisma.{{ module }}
    .findUnique(query.data)
    .then((res) => {
      if (!res) {
        statusCode = 404;
        return { error: ERROR_MAP[404] };
      } else {
        return res;
      }
    })
    .catch((err) => {
      const { errorCode, errorObject } = handlePrismaError(err);
      statusCode = errorCode;
      return { error: errorObject };
    });

  return NextResponse.json(res, { status: statusCode });
}

export async function PUT(request: Request, { params: { id } }: Props) {
  const { success, error, where } = await _validateSlugParameters(id);
  if (success === false) {
    return NextResponse.json({ error }, { status: 400 });
  }

  const requestBody: Partial<RequestType> = await request
    .json()
    .catch(() => {});

  const params = format{{ modulep }}Params(requestBody);
  const query = UpdateInputSchema.safeParse(params);

  if (query.success === false) {
    const { errorCode, errorObject } = handleZodError(query.error);
    return NextResponse.json({ error: errorObject }, { status: errorCode });
  }

  let statusCode = 200;

  const res = await prisma.{{ module }}
    .update({
      where,
      data: query.data,
    })
    .catch((err) => {
      const { errorCode, errorObject } = handlePrismaError(err);
      statusCode = errorCode;
      return { error: errorObject };
    });

  return NextResponse.json(res, { status: statusCode });
}

export async function DELETE(request: Request, { params: { id } }: Props) {
  const { success, error, where } = await _validateSlugParameters(id);
  if (success === false) {
    return NextResponse.json({ error }, { status: 400 });
  }

  let statusCode = 204;

  const res = await prisma.{{ module }}
    .delete({
      where,
    })
    .then(() => {
      return null;
    })
    .catch((err) => {
      const { errorCode, errorObject } = handlePrismaError(err);
      statusCode = errorCode;
      return { error: errorObject };
    });

  if (statusCode === 204) {
    return new Response(null, { status: 204 });
  } else {
    return NextResponse.json(res, { status: statusCode });
  }
}

```

# `src/schemas/config/ExtendedModels.ts`

```ts
{{- module := inputs.module -}}
{{- modulep := inputs.module | pascal -}}
{{- file := read output.abs -}}
{{- head := file | before "import { Ex } from './Commons';" 0 -}}
{{- body := file | after "import { Ex } from './Commons';" -1 -}}

{{ head }}
import { {{ modulep }}Schema } from '@/schemas/zod';
{{ body }}
// TODO: add description and example
export const {{ modulep }}ModelSchema = {{ modulep }}Schema.extend({
  id: {{ modulep }}Schema.shape.id.describe('{{ module }} id').openapi(Ex.{{ if inputs.isIdNumber }}number{{ else }}cuid{{ end }}),
  // xx: yy,
  createdAt: {{ modulep }}Schema.shape.createdAt
    .describe('created date')
    .openapi(Ex.date),
  updatedAt: {{ modulep }}Schema.shape.updatedAt
    .describe('updated date')
    .openapi(Ex.date),
})

```

# `src/schemas/config/models/{{ inputs.module | pascal }}Schema.ts`

```ts
{{- module := inputs.module -}}
{{- modulep := inputs.module | pascal -}}
{{- modules := inputs.module | plur -}}

import { z } from 'zod';
import * as builder from '@/schemas/BuildOpenApiSchema';
import { type {{ modulep }} as RequestType } from '@/schemas/zod';
import {
  Ex,
  {{ modulep}}ModelSchema as ModelSchema,
} from '@/schemas/config';

/**
 * PRISMA CONFIGS
 * *PrismaSelect: selected columns. if select all, leave it as {}
 * *PrismaInclude: included columns
 * format*Params: connect etc. if no relational post/put query, just return params.
 */

export const {{ modulep }}PrismaSelect = {};

// TODO: describe include
export const {{ modulep }}PrismaInclude = {};

// TODO: describe relation
export function format{{ modulep }}Params(params: Partial<RequestType>) {
  return params;
}

/**
 * OPENAPI CONFIGS
 * add describe & example
 *
 * _requestPostSchema: request body for POST request
 * _requestPutSchema: request body for PUT request. basically all are optional
 * _responseSchema: response body with relations
 */

// TODO: pick request params
const _requestPostSchema = ModelSchema.pick({
  // xx: true,
});

// TODO: extend params to optional
const _requestPutSchema = _requestPostSchema.extend({
  // xx: _requestPostSchema.shape.xxx.optional(),
});

// TODO: describe response with include
const _responseSchema = ModelSchema.merge(
  z.object({
    // xxx: yyyy
  })
);


/**
 * OPENAPI PATH CONFIG
 * path, summary, description, tags(user/cms), etc
 */

export const {{ modulep }}CreateSchema = builder.getCreateSchema(
  '/{{ modules }}',
  'create a {{ module }}',
  'create a {{ module }}',
  '{{ inputs.tag }}',
  _requestPostSchema,
  ModelSchema
);
export const {{ modulep }}FindManySchema = builder.getFindManySchema(
  '/{{ modules }}',
  'get {{ modules }} list',
  'get {{ modules }} list',
  '{{ inputs.tag }}',
  _responseSchema
);
export const {{ modulep }}FindUniqueSchema = builder.getFindUniqueSchema(
  '/{{ modules }}/{id}',
  'get a {{ module }}',
  'get a {{ module }} by id',
  '{{ inputs.tag }}',
  _responseSchema,
);
export const {{ modulep }}UpdateSchema = builder.getUpdateSchema(
  '/{{ modules }}/{id}',
  'update a {{ module }}',
  'update a {{ module }} by id',
  '{{ inputs.tag }}',
  _requestPutSchema,
  ModelSchema,
);
export const {{ modulep }}DeleteSchema = builder.getDeleteSchema(
  '/{{ modules }}/{id}',
  'delete a {{ module }}',
  'delete a {{ module }} by id',
  '{{ inputs.tag }}',
);

```

# `src/schemas/config/index.ts`

```ts
{{- body := read output.abs | before 0 -}}

{{ body }}
export * from './models/{{ inputs.module | pascal }}Schema';

```

# `tools/openapi/EntryPoints.ts`

```ts
{{- body := read output.abs | before -1 -}}

{{ body }}
  { schema: schm.{{ inputs.module | pascal }}CreateSchema, requireAuth: {{ if inputs.isAuthRequired }}true{{ else }}false{{ end }}, isMultiLines: false },
  { schema: schm.{{ inputs.module | pascal }}FindManySchema, requireAuth: {{ if inputs.isAuthRequired }}true{{ else }}false{{ end }}, isMultiLines: true },
  { schema: schm.{{ inputs.module | pascal }}FindUniqueSchema, requireAuth: {{ if inputs.isAuthRequired }}true{{ else }}false{{ end }}, isMultiLines: false, slugIdType: {{ if inputs.isIdNumber }}'int'{{ else }}'cuid'{{ end }} },
  { schema: schm.{{ inputs.module | pascal }}UpdateSchema, requireAuth: {{ if inputs.isAuthRequired }}true{{ else }}false{{ end }}, isMultiLines: false, slugIdType: {{ if inputs.isIdNumber }}'int'{{ else }}'cuid'{{ end }} },
  { schema: schm.{{ inputs.module | pascal }}DeleteSchema, requireAuth: {{ if inputs.isAuthRequired }}true{{ else }}false{{ end }}, isMultiLines: false, slugIdType: {{ if inputs.isIdNumber }}'int'{{ else }}'cuid'{{ end }} },
];

```