Next.js Api

깨진알·2024년 2월 21일

Next.js

목록 보기
4/4

Next.js Api 만들기

1. API 라우트 만들기

// endpoint
// api/short-links/[id].jsx
export default function handler(request, response) { // (req, res)
  response.send("API");
}
// request.http
GET http://localhost:3000/api/short-links/123

2. request 다루기

export default function handler(req, res) {
  res.send(req.query); // req.body, req.cookies, req.method
}

3. response 다루기

export default function handler(req, res) {
  switch (req.method) {
    case 'POST':
      res.status(201).send({ // method chaining
        title: '위키피디아 Next.js',
        url: 'https://en.wikipedia.org/wiki/Next.js',
      });
      break;
      
    case 'GET':
      res.send([
        {
          id: 'abc',
          title: '위키피디아 Next.js',
          url: 'https://en.wikipedia.org/wiki/Next.js',
        },
        {
          id: 'def',
          title: '자유게시판',
          url: 'https://codeit.kr/commnuity/general',
        },
        {
          id: 'ghi',
          title: '질문답변',
          url: 'https://www.codeit.kr/commnuity/questions',
        },
      ]);
      
    default:
      res.status(404).send();
  }
}

4. API 라우팅 정리

(1) request 핸들러 함수

/api/short-links로 들어오는 request를 처리하려면 /pages/api/shore-links.jsx 또는 /pages/api/short-links/index.jsx 경로로 파일을 만들고 아래처럼 함수를 default export하면 된다.

export default async function handler(req, res) {
  ...
}

(2) request 객체

프로퍼티 타입 설명
method 문자열 request로 들어온 HTTP 메소드 값
query 객체 쿼리 스트링이나 Next.js에서 사용하는 Params 값이 들어 있는 객체
body 자유로움 request의 바디 값
cookie 객체 request의 쿠키가 key/value로 들어 있는 객체

(3) response 객체

함수 체이닝 방식으로 사용하기 때문에, res.status(201).send()처럼 함수를 이어서 사용할 수 있다.

프로퍼티 타입 설명
status() 함수 response로 보낼 HTTP 상태 코드를 지정
send() 함수 response로 보낼 바디를 전달

(4) 참고 자료

API Routes - Request Helpers
API Routes - Response Helpers


5. MongoDB Atlas와 Mongoose

MongoDB Atlas는 MongoDB를 만드는 회사에서 운영하는 클라우드 서비스이다. 가입만하면 간편하게 클라우드로 MongoDB를 이용할 수 있다.

Mongoose라는 라이브러리는 쉽고 편리하기 때문에 MongoDB를 사용하는 자바스크립트 개발자들이 가장 많이 사용하는 라이브러리이다.

npm install mongoose

6. Next.js에서 환경 변수 사용하기

(1) 환경 변수(Environment Variable)

환경 변수는 프로그램에서 실행 환경에 따라 다른 값을 지정할 수 있는 변수이다. 꼭 웹 서비스가 아니더라도 사용하는 데이크톱 프로그램이나 터니멀에서도 이런 환경 변수를 많이 활용한다.

꼭 서버와 데이터베이스를 여러 개 쓰는 경우가 아니더라도 데이터베이스 주소와 같은 값을 소스코드에 그대로 쓰는 것은 위험하다. 반드시 환경 변수로 사용하는 것이 좋다. Node.js 환경에서는 이런 환경 변수들을 process.env라는 객체를 통해서 참조할 수 있다.

(2) Next.js에서 환경 변수 추가하기

Next.js에서는 기본적으로 dotenv라는 파이브러리를 지원한다. 이 라이브러리는 .env 같은 이름의 파일에서 환경 변수들을 저장해 두면, Node.js 프로젝트를 실행할 때 환경 변수로 지정해 주는 라이브러리이다. 이 때 주의할 점은 .env 파일 같은 건 소스 코드에 포함시키면 안 된다는 것이다.

Next.js 프로젝트에서는 기본적으로 dotenv 설정이 되어 있어서, .env.local 같은 파일을 추가하면 손쉽게 환경 변수를 추가할 수 있다.

// .env.local
MONGODB_URI=mongodb+srv://admin:blahblah@.clusterName.blahblah.mongodb.net/databaseName?retryWrites=true&w=majority

이렇게 추가한 값을 process.env.MONGODB_URI로 참조할 수 있다.

// pages/api/short-links/index.jsx
export default function handler(req, res) {
  const DB_URI = process.env.MONGODB_URI;
  // 데이터베이스 접속 ...
}

(3) Next.js에서 사용하는 특별한 환경 변수

위에서 추가한 데이터베이스 주소는 유저의 아이디와 비밀번호를 포함한 아주 민감한 정보이다. 이러한 환경 변수가 웹 사이트에 노출되는 것을 막기 위해서 Next.js에서는 클라이언트 사이드에서 사용하는 환경 벼수에 특별한 접두사(prefix)를 사용한다. NEXT_PUBLIC_이라고 이름을 붙이면 이 환경 변수는 클라이언트 사이드에서도 사용할 수 있다. 예를 들어 클라이언트 사이드에서 현재 사이트의 호스트 주소를 저장해 두고 참조하고 싶다면 NEXT_PUBLIC_HOST라는 이름으로 사용하면 된다.

MONGODB_URI=mongodb+srv://admin:blahblah@cluster0.blahblah.mongodb.net/databaseName?retryWrites=true&w=majority
NEXT_PUBLIC_HOST=http://localhost:3000
export default Home() {
  // 페이지 컴포넌트에서는 아래와 같이 사용
  return (
    <>호스트 주소: {process.env.NEXT_PUBLIC_HOST}</>
  );

7. 데이터베이스 연동하기

//db/dbConnect.jsx
import mongoose from "mongoose";
declare global {
  var mongoose: any; // This must be a `var` and not a `let / const`
}

const MONGODB_URI = process.env.MONGODB_URI!;

if (!MONGODB_URI) {
  throw new Error(
    "Please define the MONGODB_URI environment variable inside .env.local",
  );
}

let cached = global.mongoose;

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

async function dbConnect() {
  if (cached.conn) {
    return cached.conn;
  }
  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
    };
    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
      return mongoose;
    });
  }
  try {
    cached.conn = await cached.promise;
  } catch (e) {
    cached.promise = null;
    throw e;
  }

  return cached.conn;
}

export default dbConnect;

.env.local 파일도 지정해 주어야 한다.


8. 모델 만들기

// db/models/ShortLinks.jsx
import mongoose from 'mongoose';

const shortLinkSchema = new mongoose.Schema( // 모델의 구조와 프로퍼티를 정하는 것
  {
    title: { type: String, default: '' },
    url: { type: String, default: '' },
    shortUrl: { type: String, default: '' },
  }, {
    timestamps: true,
  }
);

const ShortLink = mongoose.models['ShortLink'] || mongoose.model('ShortLink', shortLinkSchema)

export deafult ShortLink;

9. 도큐먼트로 생성, 조회하기

// api/short-links/index.jsx
export default async function handler(req, res) {
  await dbConnect();
  
  switch (req.method) {
    case 'POST':
      const newShortLink = await ShortLink.create(req.body);
      res.status(201).send(newShortLink);
      break;
     
    case 'GET':
      const shortLinks = await ShortLink.find();
      res.send(shortLinks)
      break;
      
    default:
      res.status(404).send();
  }
}

// /api/short-links/[id].jsx
export default async function handler(req, res) {
  await dbConnect();
  const { id } = req.qeury;
  
  switch(req.method) {
    ...
    
    case 'GET':
      const shortLink = await ShortLink.findById(id);
      res.send(shortLink);
      break;
    ...
}

10. 도큐먼트 수정, 삭제하기

// [id].jsx
export default async function handler(req, res) {
  await dbConnect();
  const { id } = req.qeury;
  
  switch(req.method) {
    case 'PATCH':
      const updatedShortLink = await ShortLink.findByIdAndUpdate(id, req.body, {
      new: true,});
      res.send(updatedShortLink);
    ...
    
    case 'DELETE':
      await ShortLink.findByIdAndUpdate(id);
      res.status(204).send();
      break;
  }
}

11. Mongoose 문법 정리

(1) 데이터베이스 연동하기

가장 먼저 mongoose.connect() 함수를 사용해소 커넥션을 만들고 사용한다. 이때 Next.js 환경에서 커넥션을 불필요하게 여러 개 만들 수 있기 때문에, 캐싱 기법을 사용한다. 복잡할 수 있기 때문에 공식 레포지토리에 있는 코드(next.js/examples/with-mongodb-monggose)를 참고해서 구현하는 걸 권장한다.

연동이 잘 되었는지 확인하려면 mongoose.connection.readyState라는 값으로 확인할 수 있다. 문서에 따르면 접속한 상태에서는 1이라는 값이 출력된다.

import mongoose from 'mongoose';
// ...
await dbConnect();
console.log(mongoose.connection.readyState);

(2) 모델 만들기

mongoose.Schema()를 사용해서 스키마를 생성한다. 스키마는 모델이 어떤 속성을 가질지 정하는 용도이다. mongoose.model()을 사용해서 모델을 생성한다. 이때 모델의 이름을 첫 번째 아규먼트로 넘겨주는데, 이 이름은 mongoose.models[ ... ]로 참조할 수 있기 때문에 잘 지정했는지 확인해야 한다.

모듈 파일을 import할 때마다 모델을 생성하는 일이 일어나지 않도록 mongoose.models['ShortLink' || mongoose.model('ShortLink', shortLinkSchema)처럼 작성해 둔 부분도 확인해보면 된다.

import mongoose from 'mongoose';

const shortLinkSchema = new mongoose.Schema(
  {
    title: { type: String, default: '' },
    url: { type: String, default: '' },
    shortUrl: { type: String, default: '' },
  },
  {
    timestamps: true,
  }
);

const ShortLink =
  mongoose.models['ShortLink'] || mongoose.model('ShortLink', shortLinkSchema);

export default ShortLink;

(3) 모델 다루기

1. 생성 : Model.create()

아규먼트로 전달한 값으로 도큐먼트를 생성한다.

const newShortLink = await ShortLink.create({
  title: '코드잇 커뮤니티',
  url: 'https://www.codeit.kr/community/general',
});

2. 여러 개 조회 : Model.find()

조건에 맞는 모든 도큐먼트를 조회한다. 이때 조건으로 쓰이는 객체는 MongoDB의 문법을 따른다. 간단하게는 속성과 값을 key와 value로 하는 객체를 넣어줄 수 있다.

const shortLinks = await ShortLink.find(); // 모든 도큐먼트 조회

const filteredShortLinks = await ShortLink.find({ shortUrl: 'c0d317' }) // shortUrl 값이 'c0d317'인 모든 도큐먼트 조회

3. 아이디로 하나만 조회 : Model.findById()

아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 조회한다.

const shortLink = await ShortLink.findById('n3x7j5');

4. 아이디로 업데이트하기 : Model.findByIdAndUpdate()

첫 번째 아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.

const updatedShortLink = await ShortLink.findByIdAndUpdate('n3x7j5', { ... });

5. 아이디로 삭제하기 : Model.findByIdAndDelete()

아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 삭제한다.

await ShortLink.findByIdAndDelete('n3x7j5');

(4) 그 외에 자주 사용하는 함수

1. 조건으로 하나만 조회

아규먼트로 조건을 넘겨주고 해당하는 도큐먼트를 하나만 조회한다.

const shortLink = await ShortLink.findOne({ shortUrl: 'c0d317' });

2. 조건으로 업데이트하기

첫 번째 아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.

await ShortLink.updateOne({ _id: 'n3x7j5' }, { ... }); // 업데이트만 하고 업데이트 된 값을 리턴하지는 않음

const updatedShortLink = await ShortLink.findOneAndUpdate({ _id: 'n3x7j5' }, { ... });

3. 조건으로 삭제하기

아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 삭제한다.

await ShortLink.deleteOne({ _id: 'n3x7j5' }, { ... }); // 삭제만 하고  기존 값을 리턴하지는 않음

const deletedShortLink = await ShortLink.findOneAndDelete({ _id: 'n3x7j5' }, { ... });

12. 주소 단축 기능 구현

// pages/index.jsx
...
async function handleCreate(e) {
  e.prevendDefault();
  const res = await axios.post('/short-link/', { title: url, url });
  const newShortLink = res.data;
  const newShortUrl = newShortLink.shortUrl;
  setShortUrl(newShortUrl);
}
...

// short-links/new.js
export default function ShortLinkCreatePage() {
  const router = useRouter();
  
  async function handleSubmit(values) {
    await axios.post('/short-links/', values);
    router.push('/short-links/');
  }
  ...
}

13. 짧은 주소 리다이렉트하기

// [shortUrl].jsx
export async function getServerSideProps(context) {
  const { shortUrl } = context.query;
  await dbConnect();
  const shortLink = await ShortLink.findOne({ shortUrl });
  if (shortLink) {
    return {
      redirect: {
        destination: shortLink.url,
        permanent: false,
      },
    };
  }
  
  return {
    notFound: true,
  };
}

export default function ShortUrlPage() {
  return null;
}

14. 짧은 주소 목록 보여주기

// short-links/index.jsx
export async function getServerSideProps() {
  await dbConnect();
  const shortLinks = awiat ShortLink.find();
  return {
    props: {
      shortLinks: JSON.parse(JSON.stringify(shortLinks)),
    }
  }
}

export default function ShortLinkListPage({ shortLinks }) {
  return (
    ...
  )
}

15. 짧은 주소 수정, 삭제하기

// [id].jsx
export async function getServerSideProps(context) {
  const { id } = context.query;
  await dbConnect();
  const shortLink = await ShortLink.findById(id);
  if (shortLink) {
    return {
      props: {
        shortLink: JSON.parse(JSON.stringify(shortLink)),
      },
    };
  }
  return {
    notFound: true,
  };
}

export default function ShortLinkEditPage({ shortLink }) {
  const router = useRouter();
  const { id } = router.query;

  async function handleSubmit(values) {
    await axios.patch(`/short-links/${id}`, values);
    router.push("/short-links/");
  }
...
}

// index.jsx
export default function ShortLinkListPage({ shortLinks: initialShortLinks }) {
  const [shortLinks, setShortLinks] = useState(initialShortLinks);

  async function handleDelete(id) {
    await axios.delete(`/short-links/${id}`);
    setShortLinks((prevShortLinks) =>
      prevShortLinks.filter((shortLink) => shortLink._id !== id)
    );
  }
...

16. Vercel에서 환경 변수 설정하기

  1. 프로젝트 화면에서 Settings 메뉴로 이동한다.
  2. Environment Variables 메뉴로 들어간다.
  3. Vercel에서 사용할 환경 변수를 추가할 수 있다. (NEXT_PUBLIC_BASE_URL 환경 변수는 추가하고, MONGOOB_URI는 따로 지정하지 않는다.)

17. Vercel과 MongoDB Atlas 연동하기

  1. Vercel 메인 화면에서 Integrations 메뉴로 들어간다.
  2. 마켓 플레이스에서 MongoDB Atlas를 선택하고, Add Integration을 클릭해서 연동한다.
  3. 어떤 프로젝트에 적용할 지 물어보면, 모든 프로젝트에 적용하기를 한다.
  4. 우선 모든 권한을 허용해 준다.
  5. MongoDB Atlas 쪽에서 권한을 확인한다.
  6. Vercel 프로젝트와 연결한 클러스터를 물어본다. 둘을 연결해 준다. 그리고 허용할 IP 목록에 Vercel을 위해서 0.0.0.0/0을 허용한다는 것을 이해했다고 체크한다.
profile
프론트엔드 지식으로 가득찰 때까지

0개의 댓글