티스토리 뷰

 이번 개인 프로젝트를 진행하면서 headless CMS 인 strapi를 사용하고 있다. 백엔드 서버와 데이터베이스를 손쉽게 구축하고 관리할 수 있기 때문이다.  strapi는 endpoint를 이용하여 자동으로 API를 만들어 제공하지만, 사용하다보면 API를 직접 커스텀해야하는 경우도 생기기 마련이다. 나같은 경우는 회원가입 진행 시, username과 email 의 중복 검사를 진행하기 위해 API를 커스텀하여 만들었다. 

 기존의 register API 에서도 중복되는 email이나 username으로 회원가입 시도 시, 다음과 같은 응답을 보내며 회원가입을 막고있다. 하지만 이렇게 되면 사용자는 회원가입 버튼을 누른 후에야 중복여부를 알 수 있어 매우 불편한 상황이 생긴다. 이 방법이 아니라면 모든 회원 정보를 서버로부터 받아와 front 단에서 중복검사를 하는 수밖에 없지만 이 방법 또한 그리 좋은 방법은 아니라 생각했다. 

그래서 아래와 같은 데이터를 반환하는 API를 만들고자 했다. 

{ 
    is_duplicate: boolean,
    username: string,
    message:string,
}

 

공식문서에 Custom API를 만드는 방법이 잘 나와있고, github issues에 나와 비슷한 상황의 글이 존재해 도움을 받았다.

https://strapi.io/blog/how-to-create-a-custom-api-endpoint-in-strapi

https://github.com/strapi/strapi/issues/3331

 

🔧 1. 설치하기 

간편하게 CLI를 통해 설치할 수 있다. 

npx strapi generate

명령어를 치면 다음과 같이 선택 혹은 입력을 하게 되어있는데, 

API를 만들 것이므로 Strapi Generators api를 선택한다. 

API name 은 말 그대로 API의 이름을 입력하는 것인데, 이 이름이 endpoint가 된다. 나는 username의 중복을 검사하는 일종의 유효성 검사할 것이므로 username-valid라고 명명했다. 

그리고 플러그인을 만들 것이 아니기때문에 no를 선택했다. 

 

완료되면 기존의 api 폴더 하위에 우리가 입력했던 API name 의 값대로 디렉터리가 하나 생성되고, 그 하위 폴더에 controllers, routes, services 디렉터리가 생성된다. 

 

🎭 2. Routes

routes는 strapi로 보내진 Request 를 처리한다.  Strapi는 content-types으로 routes를 자동으로 생성한다. 

 

Routes/username-valid.js

module.exports = {
  routes: [
    {
     method: 'GET', // GET 요청을 받을 것
     path: '/username-valid/:username', //요청 endpoint, username을 parameter로 전달할 것
     handler: 'username-valid.usernameValidCheck', //usernameValidCheck는controller의 이름
     config: {
       policies: [],
       middlewares: [],
     },
    },
  ],
};

 

🧶 3. Service

serivce는 재사용 가능한 함수들의 집합이다. 그리고 service는 기본적으로 Koa 의 ctx 객체(요청과 응답 객체)에 대해 알지못한다.  

Service/username-valid.js 

"use strict";

/**
 * username-valid service
 */

module.exports = { 
//Routes의 handler에 적어준 것과 같게 명명
  usernameValidCheck: async (username) => {
    try {
    //findOne메서드로 username 에 해당하는 user를 찾는다.
      const entries = await strapi.entityService.findOne(
        "api::users.users",
        username,
        {
          fields: [],
          populate: "",
        }
      );
      return entries;
    } catch (err) {
      return err;
    }
  },
};

🎮 4. Controller

Controller는 acions이라 명명한 methods 를 포함하고 있다. 요청된 경로에 따라 client가 도달하는 곳이 이 파일이다. client가 route에 요청을 보내면, action method가 비지니스 로직을 실행하고, response를 보낸다. 

Contoller/username-valid.js 

"use strict";

module.exports = {
  usernameValidCheck: async (ctx) => {
    try {
      const { username } = ctx.params;

      const users = await strapi.plugins[
        "users-permissions"
      ].services.user.fetchAll(); //users 정보를 모두 불러오기

      const flag = users.find((user) => user.username === username) //일치하는 게 있는지 find 메소드로 찾기
        ? true
        : false;

      if (flag) {
        const resData = {
          is_duplicate: true,
          username,
          message: "이미 존재하는 별명입니다.",
        };
        return (ctx.res.body = resData);
      } else {
        const resData = {
          is_duplicate: false,
          username,
          message: "사용 가능한 별명입니다.",
        };
        return (ctx.res.body = resData);
      }
      
    } catch (err) {
      ctx.badRequest("product-reviews 컨트롤러에서 에러 발생", {
        moreDetails: err,
      });
    }
  },
};

 

✨ 5. 결과

http://localhost:1337/api/username-valid/:username 경로로 GET 요청을 보내면 다음과 같이 응답이 온다. 

같은 방법으로 이메일 검증 API 도 만들었다.

프로젝트에서는 아래와 같이 사용되었다. 

 

댓글