๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Next.js

[๋‚ด๋ณด๋‚ด๋ฒˆ] Next.js docs - API Middlewares

๋‚ด๋ณด๋‚ด๋ฒˆ(๋‚ด๊ฐ€ ๋ณด๋ ค๊ณ  ๋‚ด๊ฐ€ ๋ฒˆ์—ญํ•œ...) Next.js docs

2021๋…„ 4์›” 15์ผ ๊ธฐ์ค€ Next.js ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ฒˆ์—ญํ–ˆ๋‹ค.

โ€ป ์˜์–ด ์ „๊ณต์ž๋„ ํ•ด์™ธ ์œ ํ•™ํŒŒ๋„ ์•„๋‹ˆ๊ธฐ์— ๋ฒˆ์—ญ์—๋Š” ์˜์—ญ, ์˜ค์—ญ, ๊ตฌ๊ธ€ ๋ฒˆ์—ญ์ด ๋ฌด์ˆ˜ํžˆ ๋งŽ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ˜ผ์ž ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•ด๊ฐ€๋ฉฐ ๋ฒˆ์—ญํ•˜๋‹ค ๋ณด๋‹ˆ ์˜คํƒ€๋„ ๋งŽ์„ ์ˆ˜ ์žˆ๋‹ค. ์ •ํ™•ํ•œ ๋‚ด์šฉ์€ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ง์ ‘ ์‚ดํŽด๋ณด๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์ •๋ณด๋“ค์„ ๋” ์ฐพ์•„๋ณด๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.

(ํ•˜์ง€๋งŒ ๋Œ“๊ธ€ ํ”ผ๋“œ๋ฐฑ๋„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค๐Ÿ˜ƒ )

Next.js ๊ณต์‹๋ฌธ์„œ ํ™•์ธํ•˜๊ธฐ>>

 

API Routes: API Middlewares | Next.js

API Routes provide built-in middlewares that parse the incoming request. Learn more about them here.

nextjs.org


API routes provide built in middlewares which parse the incoming request (req). Those middlewares are:
API ๋ผ์šฐํŠธ๋Š” ๋ฐ›์€ ์š”์ฒญ(req)์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๋Š” ๋‚ด์žฅ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๋ฏธ๋“ค์›จ์–ด์—๋Š”: 

  • req.cookies - An object containing the cookies sent by the request. Defaults to {}
  • req.query - An object containing the query string. Defaults to {}
  • req.body - An object containing the body parsed by content-type, or null if no body was sent
  • req.cookes - ์š”์ฒญ์— ์˜ํ•ด ์ „๋‹ฌ๋ฐ›์€ ์ฟ ํ‚ค๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฐ์ฒด. ๊ธฐ๋ณธ๊ฐ’์€ {}
  • req.query - ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์„ ํฌํ•จํ•˜๋Š” ๊ฐ์ฒด. ๊ธฐ๋ณธ๊ฐ’์€ {}
  • req.body - content-type์œผ๋กœ ๊ตฌ๋ฌธ ๋ถ„์„๋œ body ํ˜น์€ ์ „์†ก๋œ body๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๋Š” null์„ ํฌํ•จํ•˜๋Š” ๊ฐ์ฒด

Custom config

Every API route can export a config object to change the default configs, which are the following:
๋ชจ๋“  API ๋ผ์šฐํŠธ๋Š” ๊ธฐ๋ณธ config๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด์„œ config ๊ฐ์ฒด๋ฅผ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š”๋ฐ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
}

The api object includes all configs available for API routes.
api ๊ฐ์ฒด๋Š” API ๋ผ์šฐํŠธ์— ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ตฌ์„ฑ์„ ํฌํ•จํ•œ๋‹ค. 

'bodyParser' Enables body parsing, you can disable it if you want to consume it as a Stream:
bodyParse๋Š” body ๊ตฌ๋ฌธ ๋ถ„์„์„ ํ™œ์„ฑํ™”ํ•˜๊ณ  Stream์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์œผ๋ฉด ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋œ๋‹ค:

export const config = {
  api: {
    bodyParser: false,
  },
}

bodyParser.sizeLimit is the maximum size allowed for the parsed body, in any format supported by bytes, like so:
bodyParser.sizeLimit์€ ๊ตฌ๋ฌธ ๋ถ„์„๋œ body์˜ ํ—ˆ์šฉ๋˜๋Š” ์ตœ๋Œ€ ํฌ๊ธฐ์— ๋Œ€ํ•œ ๊ฒƒ์œผ๋กœ bytes์—์„œ ์ง€์›ํ•˜๋Š” ์–ด๋–ค ํ˜•์‹๋„ ๊ฐ€๋Šฅํ•˜๋‹ค:

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '500kb',
    },
  },
}

externalResolver is an explicit flag that tells the server that this route is being handled by an external resolver like express or connect. Enabling this option disables warnings for unresolved requests.
externalResolver๋Š” ์ด ๋ผ์šฐํŠธ๋Š” express, connect ๊ฐ™์€ ์™ธ๋ถ€ resolver์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์„œ๋ฒ„์— ์•Œ๋ ค์ฃผ๊ธฐ ์œ„ํ•œ ๋ช…์‹œ์ ์ธ ํ”Œ๋ž˜๊ทธ์ด๋‹ค. ์ด ์˜ต์…˜์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ํ•ด๊ฒฐ๋˜์ง€ ์•Š์€ ์š”์ฒญ์— ๋Œ€ํ•œ ๊ฒฝ๊ณ ๋Š” ๋น„ํ™œ์„ฑํ™”๋œ๋‹ค. 

export const config = {
  api: {
    externalResolver: true,
  },
}

 

Connect/Express middleware support

You can also use Connect compatible middleware.
Connect ํ˜ธํ™˜ ๋ฏธ๋“ค์›จ์–ด๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

For example, configuring CORS for your API endpoint can be done leveraging the cors package.
์˜ˆ๋ฅผ ๋“ค๋ฉด, API ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ CORS ๊ตฌ์„ฑ์€ cors ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ•จ์œผ๋กœ ์ˆ˜ํ–‰๋  ์ˆ˜ ์žˆ๋‹ค. 

First, install cors:
๋จผ์ € cors๋ฅผ ์„ค์น˜ํ•œ๋‹ค:

npm i cors
# or
yarn add cors

Now, let's add cors to the API route:
์ด์ œ API ๋ผ์šฐํŠธ์— cors๋ฅผ ์ถ”๊ฐ€ํ•˜์ž:

import Cors from 'cors'

// Initializing the cors middleware
const cors = Cors({
  methods: ['GET', 'HEAD'],
})

// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result)
      }

      return resolve(result)
    })
  })
}

async function handler(req, res) {
  // Run the middleware
  await runMiddleware(req, res, cors)

  // Rest of the API logic
  res.json({ message: 'Hello Everyone!' })
}

export default handler

Go to the API Routes with CORS example to see the finished app
API Routes with CORS ์˜ˆ์ œ์—์„œ ์™„์„ฑ๋œ ์•ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

Extending the req/res objects with TypeScript

For better type-safety, it is not recommended to extend the req and res objects. Instead, use pure functions to work with them:
๋” ๋‚˜์€ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์œ„ํ•ด์„œ req์™€ res ๊ฐ์ฒด๋ฅผ ํ™•์žฅํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. 

// utils/cookies.ts

import { serialize, CookieSerializeOptions } from 'cookie'
import { NextApiResponse } from 'next'

/**
 * This sets `cookie` using the `res` object
 */

export const setCookie = (
  res: NextApiResponse,
  name: string,
  value: unknown,
  options: CookieSerializeOptions = {}
) => {
  const stringValue =
    typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value)

  if ('maxAge' in options) {
    options.expires = new Date(Date.now() + options.maxAge)
    options.maxAge /= 1000
  }

  res.setHeader('Set-Cookie', serialize(name, String(stringValue), options))
}

// pages/api/cookies.ts

import { NextApiRequest, NextApiResponse } from 'next'
import { setCookie } from '../../utils/cookies'

const handler = (req: NextApiRequest, res: NextApiResponse) => {
  // Calling our pure function using the `res` object, it will add the `set-cookie` header
  setCookie(res, 'Next.js', 'api-middleware!')
  // Return the `set-cookie` header so we can display it in the browser and show that it works!
  res.end(res.getHeader('Set-Cookie'))
}

export default handler

If you can't avoid these objects from being extended, you have to create your own type to include the extra properties:
๊ฐ์ฒด๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ๋ฐ–์— ์—†๋‹ค๋ฉด, ์ถ”๊ฐ€์ ์ธ ์†์„ฑ๋“ค์„ ํฌํ•จํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ž์‹ ๋งŒ์˜ ํƒ€์ž…์„ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค. 

// pages/api/foo.ts

import { NextApiRequest, NextApiResponse } from 'next'
import { withFoo } from 'external-lib-foo'

type NextApiRequestWithFoo = NextApiRequest & {
  foo: (bar: string) => void
}

const handler = (req: NextApiRequestWithFoo, res: NextApiResponse) => {
  req.foo('bar') // we can now use `req.foo` without type errors
  res.end('ok')
}

export default withFoo(handler)

Keep in mind this is not safe since the code will still compile even if you remove withFoo() from the export.
withFoo()๋ฅผ ๋‚ด๋ณด๋‚ด๊ธฐ์—์„œ ์ œ๊ฑฐํ•ด๋„ ์—ฌ์ „ํžˆ ์ปดํŒŒ์ผ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ์ „ํ•˜๊ธฐ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜๋ผ.