typescript - ¿Cómo puedo separar la capa de datos de las rutas API de Next.js?
Estoy usando Next.js con MongoDB. Actualmente estoy usando el cliente de MongoDB en TypeScript. Me preguntaba si más adelante tengo que cambiar mi base de datos, necesitaré personalizar el archivo api/route.ts.
En lugar de personalizar directamente el archivo route.ts, ¿podemos inyectar algún tipo de dependencia que se encargue solo de la operación de datos y abstraiga esta parte de la base de datos en un archivo separado?
Actualmente, mi archivo api/route.ts se ve como se muestra a continuación, como puedes ver está estrechamente ligado a la API de base de datos específica del proveedor.
import clientPromise from '@/app/mongodb';
import { NextResponse } from 'next/server';
import * as crypto from 'crypto';
import { v4 as uuidv4 } from "uuid";
export async function POST(request: Request) {
const ip = // get the IP
const sha256 = crypto.createHmac("sha256", process.env.SALT!);
const ipHash = sha256.update(ip!);
const ipHashDigest = ipHash.digest("hex");
const client = await clientPromise;
const db = client.db("survey-db");
const creatorHistory = await db.collection("creator-ip-hash").findOne({ ipHash: ipHashDigest });
const uuid = uuidv4();
if (creatorHistory === null) {
await db.collection("creator-ip-hash").insertOne({ ipHash: ipHashDigest, count: 1 });
} else {
const prevCount = creatorHistory.count;
if (prevCount >= 3) {
return NextResponse.json({ "error": "API Limit is reached" }, { status: 429 });
}
await db.collection("creator-ip-hash").updateOne({ ipHash: ipHashDigest }, { $set: { count: prevCount + 1 } });
}
const survey = await request.json();
survey.creatorIpHash = ipHashDigest;
survey.uuid = uuid;
await db.collection("survey-templates").insertOne(survey);
return NextResponse.json({ "survey-id": uuid }, { status: 201 });
}
Sé que POST no es una clase sino una función, pero aun así hay una manera de inyectar dependencia de alguna forma. Estaba mirando esta guía: https://himynameistim.com/blog/dependency-injection-with-nextjs-and-typescript pero parece que tal vez es para una versión anterior y tampoco muestra cómo incorporar esto en las rutas de API. Han mostrado el uso de inject y injectable pero es para clases.
Encontré esta discusión en la comunidad de GitHub de next.js: https://github.com/vercel/next.js/discussions/46805
Parece que tenemos que trabajar con package.json o webpack, aún no puedo encontrar ninguna guía adecuada sobre cómo usar esto en las rutas de la API. Ellos están usando tsyringe para el contenedor de inyección de dependencias.
Sin embargo, estoy abierto a cualquier solución que simplemente desacople esta capa de acceso a datos de las rutas de la API, de modo que en el futuro, si tengo que cambiar el backend de la base de datos, pueda hacerlo de manera eficiente. Si alguien lo ha hecho antes, por favor hágame saber su idea.
Respuesta #1
Para separar la capa de datos de tus rutas API de Next.js y hacer que tu código sea más modular y agnóstico a la base de datos, puedes crear una capa de acceso a datos que abstraiga el código específico de la base de datos. Aquí te mostramos cómo puedes lograr esto:
Crear una capa de acceso a datos:
Comience creando un nuevo directorio en su proyecto para la capa de acceso a datos. Puede llamarlo algo como data-access o cualquier nombre que tenga sentido para usted.
/data-access
- surveys.ts
En el archivo surveys.ts (u otros archivos según sea necesario), define funciones que encapsulen operaciones de base de datos para encuestas. Estas funciones deben estar escritas de manera que no dependan de ninguna implementación específica de base de datos.
// data-access/surveys.ts
import { Db, ObjectId } from 'mongodb';
export interface Survey {
// Define your survey schema here
}
export class SurveyDataAccess {
private readonly db: Db;
constructor(db: Db) {
this.db = db;
}
async createSurvey(survey: Survey): Promise<string> {
const result = await this.db.collection('survey-templates').insertOne(survey);
return result.insertedId.toString();
}
async getSurveyById(id: string): Promise<Survey | null> {
const survey = await this.db.collection('survey-templates').findOne({ _id: new ObjectId(id) });
return survey;
}
// Add other data access methods as needed
}
Inyectar dependencia de base de datos:
En tus rutas de API de Next.js, inyecta la dependencia de la base de datos creando una instancia de SurveyDataAccess y pasando la conexión de la base de datos como un parámetro al constructor.
// api/surveys.ts
import { NextApiResponse } from 'next';
import { SurveyDataAccess } from '../data-access/surveys';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const client = await clientPromise; // Assuming clientPromise is your database connection
const surveyDataAccess = new SurveyDataAccess(client.db('survey-db'));
if (req.method === 'POST') {
// Use surveyDataAccess to create a new survey
// ...
} else if (req.method === 'GET') {
// Use surveyDataAccess to retrieve surveys
// ...
}
}
Abstraer el código específico de la base de datos:
En tus rutas de API, utiliza el objeto surveyDataAccess para interactuar con la base de datos en lugar de llamar directamente al código específico de MongoDB. Esta abstracción te permite cambiar a una base de datos diferente en el futuro sin modificar tus rutas de API.
Pruebas y Mantenimiento:
Con esta separación, puedes escribir fácilmente pruebas unitarias para tu capa de acceso a datos independientemente de tus rutas de API. Si decides cambiar a una base de datos diferente en el futuro, solo necesitas actualizar la clase SurveyDataAccess y mantener tus rutas de API sin cambios.
Al seguir este enfoque, logras una separación de responsabilidades y haces que tu código sea más mantenible y adaptable a cambios futuros en la capa de la base de datos.
Respuesta #2
si tu pregunta está relacionada con configurar una conexión a la base de datos similar a express.js, no podemos hacer esto en next.js. Lo expliqué aquí por qué. En Express.js, las conexiones a la base de datos suelen ser de larga duración, lo que significa que se establecen cuando la aplicación se inicia y permanecen activas durante toda la vida de la aplicación. Pero en next.js, debido a la naturaleza de las funciones serverless, tenemos que implementar la conexión por solicitud.
Pero si quieres implementar inyección de dependencias, puedes probar tsyringe desarrollado por Microsoft. Para aplicar esto en next.js, puedes seguir esto. En caso de que el enlace se rompa en el futuro:
// reflect metadata allows you to use decoraters
npm install --save tsyringe reflect-metadata
Modifica tu archivo tsconfig.json para incluir la siguiente configuración, lo cual permitirá el uso de decoradores en TypeScript.
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Agrega un archivo .bablerc a tu proyecto con los siguientes ajustes.
{
"presets": ["next/babel"],
"plugins": [
# those implements the decorators
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
Finalmente, agrega esta importación a tu archivo _app.tsx o en el directorio app para el diseño.
import "reflect-metadata";
Ahora estamos listos para convertir el código que tenía antes en algo más mantenible.
El código de ejemplo crea un cliente de GraphQL:
import { DocumentNode } from "graphql";
export interface graphClient {
query(query: DocumentNode, variables?: {}): any;
}
nota que necesitas usar componentes de clase para inyectar en el constructor
import { inject, injectable } from "tsyringe";
@injectable()
export class getLatestPosts implements iGetLatestPosts {
graphClient: graphClient;
constructor(@inject("graphClient") private graphClientParam: graphClient) {
this.graphClient = graphClientParam;
}
...
}
