Search code examples
typescriptexpressnext.jscorsvercel

Next with vercel deployment - How to achieve a reusable CORS middleware on api?


I've been playing around trying to avoid a CORS error with my next app which is deployed on vercel. The only way I managed to do this was by manually setting the headers on each api request... eg:

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.setHeader('Access-Control-Allow-Credentials', 'true')
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.setHeader('Access-Control-Allow-Methods', 'GET')
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept, Authorization'
  )

  // Handle pre-flight requests
  if (req.method === 'OPTIONS') {
    res.status(200).end()
    return
  }

  let houses: House[] = []

  try {
    await client.connect()
    console.log('Connected to MongoDB')

    const db = client.db('vroom')
    const housesCollection = db.collection('houses')

    houses = (await housesCollection.find().toArray()).map((house) => ({
      house: house.house,
      price: house.price,
      timestamp: house.timestamp,
      developer: house.developer,
    }))

    console.log('Retrieved houses:', houses)
  } catch (error) {
    console.error(`Error connecting to MongoDB: ${error}`)
  } finally {
    await client.close()
  }

  res.json({ houses })
}

Is it possible to achieve the same result either via the middleware.ts file, or through some sort of wrapper code?

I hope this makes sense but please ask for any clarification if not!

Thanks

I tried the above but have to enter some text here :)


Solution

  • I managed to do the next best thing and create a wrapper for my apis facing that cors error:

    here's a utils/middlware.ts file:

    import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
    import Cors from 'cors'
    
    const corsMiddleware = Cors({
      methods: ['GET', 'OPTIONS', 'HEAD'],
    })
    
    const runMiddleware = (
      req: NextApiRequest,
      res: NextApiResponse,
      fn: Function
    ) => {
      return new Promise((resolve, reject) => {
        fn(req, res, (result: any) => {
          if (result instanceof Error) {
            return reject(result)
          }
    
          return resolve(result)
        })
      })
    }
    
    export const withCors =
      (handler: NextApiHandler) =>
      async (req: NextApiRequest, res: NextApiResponse) => {
        await runMiddleware(req, res, corsMiddleware)
    
        if (req.method === 'OPTIONS') {
          res.status(200).end()
          return
        }
    
        return handler(req, res)
      }
    

    Which can then be used like so:

    import { NextApiRequest, NextApiResponse } from 'next'
    import { connectToDB, paginateCollection, withCors } from '@/utils'
    
    export type House = {
      house: string
      price: string
      timestamp: string
      developer: string
    }
    
    const handler = async (req: NextApiRequest, res: NextApiResponse) => {
      const page = parseInt(req.query.page as string) || 1
      const perPage = 10
    
      let houses: House[] = []
      let totalPages = 1
    
      try {
        const db = await connectToDB()
    
        const housesCollection = db.collection('foo')
    
        const { items, totalPages: pages } = await paginateCollection(
          housesCollection,
          page,
          perPage
        )
        totalPages = pages
    
        houses = items.map((house) => ({
          house: house.house,
          price: house.price,
          timestamp: house.timestamp,
          developer: house.developer,
        }))
      } catch (error) {
        console.error(`Error connecting to MongoDB: ${error}`)
      }
    
      res.json({ houses, totalPages })
    }
    
    export default withCors(handler)
    

    Hope that helps someone else!