OpenAPI Spec과 Swagger 덕에 API Spec을 매우 편하게 공유하고 관리할 수 있게 되었다. 하지만 제아무리 API Spec이 보기 쉽게 공유된다 하더라도, 해당 API를 사용하는 애플리케이션을 개발하는 과정에서 발생할 수 있는 인간의 실수를 무시할 수 없을 뿐 아니라, 이 과정이 여간 귀찮은 게 아니다. 하지만 이런 귀찮은 일을 하기 싫어하는 게으른 개발자들은 세상을 꾸준히 평화롭게 만들어주고 있다.
들어가며
2000년, Roy Fielding에 의해 REST가 소개된 이후로 REST API는 현재까지도 웹 애플리케이션 개발에서 매우 중요한 축을 담당하고 있으며, 현재까지도 많은 웹 애플리케이션이 REST API를 사용 및 제공하고 있다. 많은 경우의 개발과정 및 유지보수에서 REST API를 설계 및 관리할 때, OpenAPI Specification의 형태로 관리하며, Swagger등의 툴을 이용해 시각화하여 개발자 간의 의사소통에 사용되곤 한다.
Swagger Demo
좋다. Swagger는 미려한 UI를 제공하며, API의 상세를 참으로 알기 편하게 해준다. 하지만 역시 이걸 보면서 API의 URI path를 쓰고, 파라미터의 이름과 타입, validation을 한땀 한땀 써내려가다보면, 내가 제대로 받아 적고 있기는 한건지 걱정될 뿐 아니라, 매우, 귀찮다.
여기서 restful-react라는 빛을 발견하였고, 가볍게 사용해본 결과 내 삶은 약간 더 평화로워졌다. 따라서 나는 오늘 여기서 약간의 내면의 평화를 얻는 방법을 공유하려 한다. Next.js를 이용하여 TODO 애플리케이션을 만들면서, restful-react를 활용하여 OpenAPI Spec으로부터 React Hook을 생성하는 방법을 살펴볼 것이다. 그리고 약간의 덤으로, OpenAPI Spec을 매우 편리하게 쓸 수 있는 Stoplight Studio도 소개하려 한다.
애플리케이션 설계
OpenAPI Spec을 써내려가기 앞서, 지금부터 만들고자 하는 애플리케이션의 요구사항부터 정리해야 한다. 오늘 우리의 목적은 애플리케이션을 멋지게 만드는 게 아니라, API 인터페이스를 한땀 한땀 수 놓는 귀찮음으로부터 해방되어 내면의 평화를 얻는 것이다. 요구사항은 간단하게 가자.
- TODO 리스트를 보여준다
- 각 TODO는 날짜와 내용을 가지고 있다
- TODO 리스트에 새로운 TODO를 추가할 수 있다
너무도 간단한 요구사항이라 벌써부터 내면의 평화가 찾아오려 한다. 하지만 진짜 평화는 따로 있으니까 아직은 때가 아니다.
Stoplight Studio를 이용한 OpenAPI Spec 배출
이제 위 요구사항을 만족시킬 수 있는 REST API를 설계해야 한다. 여기서 Stoplight Studio를 사용할 것이다. Stoplight Studio의 소개를 간단하게 보자.
Design APIs 10x faster with our free OpenAPI editor. Prototype and share your API within minutes.
간단하게 말하면, 공짜로 겁나 빠르게 OpenAPI를 쓰고 공유할 수 있다는 얘기다. 심지어 이 멋진 OSS는 웹 버전도 있어 간단히 사용할 수 있다. Stoplight Studio 페이지에 가면, 웹 버전으로 통하는 링크가 있을 것이다. 여기서 로컬 프로젝트를 하나 생성한다.
Stoplight Studio에서 새 프로젝트 생성
새 프로젝트를 생성하고 들어서면, 너무나도 직관적인 메뉴들이 늘어서 있다. todo API를 추가해보자. 빈 껍데기 뿐인 API Overview를 볼 수 있다. 좌하단의 Paths에 우리가 필요한 API들의 경로를 추가할 수 있다.
todo API와 todos 경로 추가
위 요구사항을 만족할 수 있으려면 몇 개의 API가 필요할까? 자세한 설명은 생략하고 결과부터 보자.
- GET /todos: TODO list를 몽땅 가져오는 API. 파라미터는 없다.
- POST /todos: TODO를 추가하는 API. Request body에 TODO의 내용을 JSON 포맷으로 넣어줄 것이다.
그리고 이 API에 필요한 모델로는 Todo가 있겠다. Paths 밑에 있는 Models에 추가해준다. Stoplight Studio는 너무도 직관적이며, REST API와 OpenAPI Spec에 대한 기본적인 이해만 바탕에 있으면 사용에 무리가 없을 것이다. 따라서 이걸 소개하느라 너무 많은 시간을 쓸 수는 없다. 어디까지나 이건 덤이니까.
Stoplight Studio를 통해 OpenAPI Spec을 완성하고 나면 우상단의 Code를 눌러 다음과 같은 OpenAPI Spec을 얻을 수 있다.
1 | openapi: 3.0.0 |
2 | info: |
3 | title: todo |
4 | version: '1.0' |
5 | description: TODO application for our inner peace... |
6 | servers: |
7 | - url: 'http://localhost:3000' |
8 | paths: |
9 | /todos: |
10 | get: |
11 | summary: Get the todo list |
12 | tags: |
13 | - todo |
14 | responses: |
15 | '200': |
16 | description: OK |
17 | content: |
18 | application/json: |
19 | schema: |
20 | $ref: '#/components/schemas/Todos' |
21 | operationId: get-todos |
22 | post: |
23 | summary: Create a new todo |
24 | operationId: post-todos |
25 | responses: |
26 | '200': |
27 | description: OK |
28 | requestBody: |
29 | content: |
30 | application/json: |
31 | schema: |
32 | $ref: '#/components/schemas/Todo' |
33 | tags: |
34 | - todo |
35 | components: |
36 | schemas: |
37 | Todos: |
38 | title: Todos |
39 | type: object |
40 | properties: |
41 | todos: |
42 | type: array |
43 | items: |
44 | $ref: '#/components/schemas/Todo' |
45 | x-tags: |
46 | - todo |
47 | description: '' |
48 | Todo: |
49 | title: Todo |
50 | type: object |
51 | x-tags: |
52 | - todo |
53 | properties: |
54 | id: |
55 | type: integer |
56 | due_date: |
57 | type: string |
58 | title: |
59 | type: string |
60 | securitySchemes: {} |
TODO 애플리케이션
이제 위에서 설계한 API를 소비하는 클라이언트 애플리케이션을 만들 것이다. 사용할 프레임워크는 React의 확장인 Next.js다. React도 무관하다. npm을 이용해 간단하게 프로젝트를 시작할 수 있다.
1 | $ npm init next-app |
2 | $ cd todo-for-inner-peace |
난 TypeScript를 좋아하니까, TypeScript도 설치한다.
1 | $ npm i --save-dev typescript @types/react @types/node |
restful-react
이제야 겨우 본론에 들어왔다. 위에서 생성한 OpenAPI Spec을 바탕으로, restful-react를 이용해 fetcher를 생성할 것이다.
그러려면 우선 restful-react를 설치해야 한다.
1 | $ npm i --save restful-react |
위에서 생성한 yaml 파일을 적당한 위치에 두고, fetcher를 생성해보자. 성공하면 다음과 같은 메시지를 볼 수 있다.
1 | $ npx restful-react import --file api/todo.v1.yaml --output api/fetchers.tsx |
2 | 🎉 Your OpenAPI spec has been converted into ready to use restful-react components! |
GET /todos
이제 GET /todos
API를 호출하는 Hook을 restful-react가 생성해준 fetcher를 이용해 만들어볼 것이다. restful-react에 의해 생성된 fetchers.tsx
를 살펴보면 useGetTodos
를 찾을 수 있다. OpenAPI Spec에 기술한 GET /todos
를 호출하여, response를 React Hook으로 만들어주는 역할을 한다.
GET /todos
API에서 데이터를 받아 렌더링 해보자. 코드는 다음과 같은 모습이 될 것이다.
1 | // pages/index.tsx |
2 | const Home: NextPage = () => { |
3 | const { data: getTodos } = useGetTodos({}) |
4 | |
5 | return ( |
6 | <div> |
7 | {getTodos?.todos?.map(todo => ( |
8 | <p> |
9 | due_date: {todo.due_date}, title: {todo.title} |
10 | </p> |
11 | ))} |
12 | </div> |
13 | ) |
14 | } |
Mocking the API
위에서 간단하게 짠 코드가 제대로 동작하는 지 확인하고 싶은데, 우리는 아직 API Spec만 있을 뿐, 실제 동작하는 API를 가지고 있지 않다. 그렇다고 API 서버를 세우자니, 너무도 귀찮다. 애초에 내가 이걸 시작한 이유가 뭐였는가, 귀찮아서였다. 초심을 잃지 말자.
우리가 위에서 OpenAPI Spec을 생성하는 데에 사용했던 Stoplight Studio를 개발한 Stoplight사에서는 Prism이라는 라이브러리도 개발하고 있다. Prism의 사용법은 꽤나 간단하지만, 커맨드라인에서 일일히 사용하는 것이 귀찮다면 Stoplight Studio App을 다운로드 하는 것을 추천한다. 애플리케이션은 Mac, Windows 그리고 Linux 버전까지 지원하고 있다.
위에서는 웹 버전으로 OpenAPI Spec을 설계하였다. 동일한 인터페이스를 제공하는 애플리케이션을 다운로드 하여 실행하면, 자동으로 OpenAPI Spec으로 설계한 API들을 Mocking 해준다.
Stoplight Studio App이 제공하는 Mock Server
curl을 이용해 Mocking이 제대로 되고 있는지 확인해보자.
1 | $ curl http://127.0.0.1:3100/todos |
2 | {"todos":[{"id":-88158399,"due_date":"enim cillum veni","title":"ut nulla tempor dolore dolor"},{"id":79437924,"due_date":"consequat cupidatat sunt ut","title":"in"},{"id":31130696,"due_date":"commodo anim sint","title":"minim consectetur id"}]} |
id
나 due_date
의 상태가 영 좋지 못하지만 타입은 맞춰주고 있으며, Spec에 Validation을 꼼꼼히 적지 않은 탓도 있으니 넘어가자. Mocking은 잘 되고 있다.
RestfulProvider
이제 restful-react의 RestfulProvider
API를 이용해 우리 todos API의 base URL을 설정해주어야 한다.
App을 RestfulProvider
API로 감싸주면 되는데, next.js라면 pages/_app.tsx
파일을 생성하여 다음과 같이 설정할 수 있다.
1 | // pages/_app.tsx |
2 | function MyApp({ Component, pageProps }: AppProps) { |
3 | return ( |
4 | <RestfulProvider |
5 | base="http://127.0.0.1:3100" |
6 | > |
7 | <Component {...pageProps} /> |
8 | </RestfulProvider> |
9 | ) |
10 | } |
11 |
|
12 | export default MyApp |
이제 앱을 기동해 확인해보자.
Todo list를 Mock server로부터 가져와 표시
POST /todos
Todo를 추가하기 위한 POST method API의 경우 Request body가 필요하다. 적절한 input들을 만들고, 버튼을 눌렀을 때 input들을 request body에 담아 API를 호출한다.
1 | const Home: NextPage = () => { |
2 | const { data: getTodos } = useGetTodos({}) |
3 | const { mutate: postTodos } = usePostTodos({}) |
4 | const [ state, setState ] = React.useState({ |
5 | dueDate: '', |
6 | title: '', |
7 | }) |
8 | |
9 | const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e: React.ChangeEvent<HTMLInputElement>) => { |
10 | const { name, value } = e.target |
11 | setState(prevState => ({ ...prevState, [name]: value})) |
12 | } |
13 | const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e: React.MouseEvent<HTMLButtonElement>) => { |
14 | postTodos({ |
15 | due_date: state.dueDate, |
16 | title: state.title, |
17 | }) |
18 | } |
19 | |
20 | return ( |
21 | <div> |
22 | <label>TODO: </label> |
23 | <input |
24 | type="text" |
25 | name="title" |
26 | value={state.title} |
27 | onChange={handleChange} |
28 | /> |
29 | <br /> |
30 | <label>due date: </label> |
31 | <input |
32 | type="text" |
33 | name="dueDate" |
34 | value={state.dueDate} |
35 | onChange={handleChange} |
36 | /> |
37 | <br /> |
38 | <button onClick={handleClick}>TODO등록</button> |
39 |
|
40 | {getTodos?.todos?.map(todo => ( |
41 | <p> |
42 | due_date: {todo.due_date}, title: {todo.title} |
43 | </p> |
44 | ))} |
45 | </div> |
46 | ) |
47 | } |
완성된 애플리케이션의 모습