moxystudio / next-rest-api

Aims to ease the development of REST APIs in Next.js
MIT License
4 stars 2 forks source link

Adding Typescript Support #16

Open mastepanoski opened 2 years ago

mastepanoski commented 2 years ago

You could add support to typescript, rewriting withRest or adding compatibility through index.d.ts:

  import type { NextApiRequest, NextApiResponse } from 'next'
  import Boom from '@hapi/boom'
  import Joi from 'joi'

  const defaultLogError = (err: Boom.Boom) => {
    // Only log internal server errors
    if (!err.isServer) {
      return
    }

    // Log original error if passed
    if (err.data && err.data.originalError) {
      err = err.data.originalError
    }

    console.error(err.stack)
  }

  const defaultSendError = (res: NextApiResponse, err: Boom.Boom) => {
    const { output } = err
    const { headers, statusCode, payload } = output

    Object.entries(headers).forEach(([key, value]) =>
      res.setHeader(key, value || '')
    )

    res.status(statusCode).json(payload)
  }

  /**
   * Wraps a HTTP request handler with validation against Joi schemas.
   *
   * @param {object} schemas - An object with `query`, `body` or `headers` keys and their associated Joi schemas.
   *                           Each of these schemas will be matched against the incoming request.
   *
   * @returns {Function} The HTTP handler that validates the request.
   *
   * @example
   *
   * const getSchema = {
   *   query: Joi.object({
   *      id: Joi.string().required(),
   *   }),
   * };
   *
   * export default withRest({
   *   GET: withValidation(getSchema)(async req, res) => {
   *     // Do something with `req.query.id`
   *
   *     return { foo: 'bar' };
   *   },
   * });
   */
  export const withValidation =
    <T = any, R = any>(schemas: Joi.PartialSchemaMap<T> | undefined) =>
    (fn: (arg0: NextApiRequest, arg1: NextApiResponse<R>) => Promise<any>) =>
    async (req: NextApiRequest, res: NextApiResponse) => {
      const joiSchema = Joi.object(schemas).unknown(true)

      let validated: { [x: string]: any }

      try {
        validated = await joiSchema.validateAsync(req)
      } catch (err: any) {
        throw Boom.badRequest(err.message, { originalError: err as Error })
      }

      // Joi normalizes values, so we must copy things back to req
      ;['headers', 'body', 'query'].forEach((key: string) => {
        ;(req as any)[key] = validated[key]
      })

      return fn(req, res)
    }

  /**
   * @typedef {Function} SendError
   *
   * @param {object} res - Node.js response object.
   * @param {Error} err - The Boom error object.
   */
  /**
   * @typedef {Function} LogError
   *
   * @param {Error} err - The Boom error object.
   */

  /**
   * Matches handlers defined in `methods` against the HTTP method, like `GET` or `POST`.
   *
   * @param {object.<string, Function>} methods - An object mapping HTTP methods to their handlers.
   * @param {object} options - The options.
   * @param {SendError} options.sendError - A function responsible to send Boom errors back to the client.
   * @param {LogError} options.logError - A function that logs errors.
   *
   * @returns {Function} The composed HTTP handler.
   *
   * @example
   *
   * export default withRest({
   *   GET: async (req, res) => {
   *     // Do something...
   *
   *     return { foo: 'bar' };
   *   },
   * });
   */
  const withRest = (
    methods: {
      [x: string]: any
    },
    opts: {
      logError?: typeof defaultLogError
      sendError?: typeof defaultSendError
    } = {
      logError: defaultLogError,
      sendError: defaultSendError,
    }
  ) => {
    const options = {
      logError: defaultLogError,
      sendError: defaultSendError,
      ...opts,
    }

    return async (req: NextApiRequest, res: NextApiResponse) => {
      try {
        const method = methods && methods[req.method || 'unknown']

        if (!method) {
          throw Boom.methodNotAllowed(
            `Method ${req.method} is not supported for this endpoint`
          )
        }

        const json = await method(req, res)

        // Do nothing if the request is already sent (e.g.: a redirect was issued)
        if (res.headersSent) {
          if (json !== undefined) {
            options.logError(
              Boom.internal(
                'You have sent the response inside your handler but still returned something. This error was not sent to the client, however you should probably not return a value in the handler.'
              ) // eslint-disable-line max-len
            )
          }

          return
        }

        // Next.js doesn't support nulls as `RFC7159` dictates, but we do
        if (json == null) {
          res.setHeader('Content-Type', 'application/json')
          res.setHeader('Content-Length', '4')
          res.end('null')
        } else {
          res.json(json)
        }
      } catch (err: Error | Boom.Boom | any) {
        // Not an ApiError? Then wrap it into an ApiError and log it.
        let apiError = err
        if (!err.isBoom) {
          apiError = Boom.internal(undefined, { originalError: err })
        }

        options.logError(apiError)
        options.sendError(res, apiError)
      }
    }
  }

  export default withRest
DavideCarvalho commented 2 years ago

I think this library is not maintained anymore