서버개발자가 되는법 - #6 To-Do 앱 만들기 [2], 기획과 화면을 보고 만들어보기

     

목차 - 2020/09/29 - [Study/서버] - 서버개발자가 되는법 - 목차

 

git - tkdlek11112/server_dev at 서버개발자_6 (github.com)

git(화면) - tkdlek11112/todo-list (github.com)

 

유튜브 - 바쁘신분들은 유튜브 스킵해도 됩니다~




 

들어가기 전에

 

지난 시간에 만들어진 로그인 화면을 기초로 서버 사이드를 개발하는 과정을 다루었습니다. 이번 시간에는 메인 기능인 To-Do 기능을 만들어 볼 예정입니다.

 

클라이언트의 경우 지난 시간에 받은 docker를 사용하면 됩니다.

 

이 화면!

 

 

 

 

화면 맛보기

 

먼저 [예제 2] 버튼을 눌러서 클라이언트 환경에서 돌아가는 화면을 체험해봅니다. 역시 화면 하단 부분에 이 화면에서 구현되어야 하는 기능과 API 스펙이 적혀있습니다.

 

- 서버에 있는 To-Do 목록을 가져와서 화면에 표시한다.
- To-Do 체크 버튼을 누르면 Done을 True(or False)로 바꿔준다.
- To-Do 삭제 버튼을 누르면 리스트(DB)에서 삭제한다.
- +버튼을 눌러 만들기를 하면 새로 To-Do를 만든다.

 

+ 버튼을 누른 다음 한글로 아무거나 입력하고 엔터를 누르면 목록에 추가됩니다.

 

목록에 추가하기
아래쪽에 추가됩니다
체크도 되고 삭제도 되고

 

* 전체 조회 API
URL : http://localhost:8000/todo/select

입력필드
없음

출력필드
tasks (Array) : [
id (int): To-Do 고유 아이디
name (string): To-Do 이름
done (boolean): To-Do 완료 여부
]

* To-Do 추가 API
URL : http://localhost:8000/todo/create

입력필드
user_id (string) : 사용자 id
todo_id (int) : To-Do 고유 아이디
name (string) : To-Do 이름

출력필드
없음

* To-Do 삭제 API
URL : http://localhost:8000/todo/delete

입력필드
todo_id (int) : To-Do 고유 아이디

출력필드
없음

* To-Do 완료 API
URL : http://localhost:8000/todo/toggle

입력필드
todo_id (int) : To-Do 고유 아이디

출력필드
없음

 

 

API가 상당히 많네요. CRUD 각각 1개씩 있는 것 같습니다 ㅎㅎ

 

아 그리고 이번 강의를 진행하면서 API가 다 post로 되어있는 것을 의아해하시는 분이 있을 수 있는데요, rest를 배울 때 조회는 get, 수정은 post 이런 식으로 고정해서 쓴다고 배우신 분이 많을 겁니다. 물론 rest의 정석은 그게 맞긴 하는데, must는 아닙니다. 

 

대부분의 서비스들이 http 메서드(get, post, put, delete)로 액션을 정의하고 사용하기가 어렵습니다. 어떤 서비스는 조회하는 동시에 수정도 하기도 하구요. 따라서 대부분 실무에서는 모두 post로 사용하고 있는 추세입니다. rest의 장점은 url로 서비스를 구분할 수 있다 정도만 사용하고 있습니다 ㅎㅎ 아 물론 정석적으로 restful api를 사용하는 회사도 있을 겁니다. (저는 못 봤지만요)

 

다시 화면으로 돌아와서 조회 API에서 출력 필드에 대괄호가 쳐있는 것을 알 수 있는데, 이건 ARRAY라는 표현입니다. 제가 임의로 표현한 거지 이게 정석은 아닙니다 ㅋㅋ 실제 출력은 이런 모양입니다.

{
    "tasks":[{
    	"id" : 1,
        "name" : "스피킹 맥스 하루 학습 100% 채우기",
        "done" : false
    },
    {
    	"id" : 2,
        "name" : "운동아무거나하기",
        "done" : false
    },
    {
    	"id" : 3,
        "name" : "해리포터 책 읽기",
        "done" : false
    },
    {
    	"id" : 4,
        "name" : "이마트에서 생수 주문하기",
        "done" : false
    }]
}    

 

현재 화면에서는 입력은 아무것도 없고 출력만 ARRAY형태로 옵니다. 사용자의 id가 필요가 없이 그냥 전체 To-Do를 주면 됩니다. 

 

조회에서 id값을 받으면 "완료"와 "삭제" API에서는 이 id값을 가지고 해당 To-Do를 완료하거나 삭제하게 됩니다. 따라서 해당 api에서는 입력으로 올라온 id를 가지고 DB에서 값을 변경하거나 삭제하는 로직이 들어가야 합니다.

 

 

 

 

조회 API 개발

 

일단 화면에 뭐라도 보여야 작업을 하니까 조회를 먼저 만들어 볼까요?

 

근데 제가 이미 서버개발자_5브랜치에 To-Do 소스를 만들어놨습니다. 원래 뺐어야 했는데 ㅎㅎㅎ

 

따라 하실 분은 기존에 코드를 지우고 하시면 되고, 그냥 눈으로 따라가실 분은 코드 보시면 됩니다 :)

 

 

* 전체 조회 API
URL : http://localhost:8000/todo/select

입력필드
없음

출력필드
tasks (Array) : [
id (int): To-Do 고유 아이디
name (string): To-Do 이름
done (boolean): To-Do 완료 여부
]

 

조회는 생각보다 간단합니다. API가 콜 되면 db에 있는 To-Do 목록을 필요한 값만 리스트로 만들어서 리턴해주면 됩니다. 여기서 필요한 값은 id, name, done 값이겠네여. 

 

처음에 to-do를 만들었을 때랑 모델이 살짝 바뀌었으니 models.py를 참조 바랍니다.

 

# ~/todo/models.py

from django.db import models
from django.utils import timezone


# Create your models here.
class Task(models.Model):
    user_id = models.CharField(max_length=20, null=False, default=False)
    name = models.CharField(verbose_name="작업이름", max_length=256, null=False, default='')
    start_date = models.DateField(verbose_name="시작날짜", default=timezone.now)
    end_date = models.DateField(verbose_name="마감날짜", null=True)
    finish_date = models.DateField(verbose_name="완료날짜", null=True)
    state = models.IntegerField(verbose_name="상태", null=False, default=0)
    done = models.BooleanField(verbose_name="상태", null=False, default=False)

    class Meta:
        db_table = 'task'
        verbose_name = '작업(to-do) 테이블'

 

models.py에서 대부분은 현재 쓰이지 않는 필드지만 추후 To-Do 목록 조회가 고도화되면 사용될 필드들입니다. 

 

지금 사용하는 filed값은 'id', 'name', 'done'입니다. id는 따로 선언되지 않았지만 default로 잡혀있기에 그냥 사용하면 됩니다. (따로 pk 설정을 하지 않으면 id라는 필드가 기본적으로 pk로 생성되게 되어있습니다.)

 

# ~/todo/views.py

class TaskSelect(APIView):
    def post(self, request):
        # 현재 특정 사용자 구분을 하지 않기 때문에 전체를 조회해서 뿌려줌
        tasks = Task.objects.filter()

        task_list = []
        for task in tasks:
            task_list.append(dict(id=task.id,
                                  name=task.name,
                                  done=task.done))

        return Response(dict(tasks=task_list))
# ~/todo/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('select', views.TaskSelect.as_view(), name='select'),
]

 

원래는 user_id를 기반으로 그 사용자의 To-Do 목록만 조회하는 게 맞지만, 현재는 사용자 구분하지 않고 전체를 내려주게 되어있습니다. 따라서 input값은 아무것도 없고 output값으로 task_list를 만들어 클라이언트에 전달합니다. task_list의 key값은 'tasks'입니다. 'task_list'로 착각하지 마세요~

 

소스를 추가하고 [실습 2]를 누르면 아무것도 뜨지 않는 것을 확인할 수 있습니다. 왜냐하면 DB에 데이터가 없기 때문이죠, ( git clone으로 받으신 분들은 sqlite까지 받으셔서 데이터가 보입니다! )

 

임의로 데이터를 추가하고 싶으면 DB에 row를 추가해줍니다.

 

DB에 +버튼으로 row 추가

파이참 database메뉴에서 테이블에 컬럼을 추가할 수 있습니다. + 버튼을 눌러서도 되고, insert 쿼리를 사용하셔도 됩니다. 어차피 user_id는 보지 않기 때문에 아무 값이나 넣어주시면 됩니다. 실제로 필요한 건 id, name, done 세 가지입니다. 여기서 id는 빈 값으로 생성해도 자동으로 생성되기 때문에 넣지 않으시는 게 좋습니다.

 

4개 정도 넣어보고 다시 화면에서 [실습 2]를 눌러봅니다.

 

DB에 넣은 값이 나오는지 확인

 

 

 

추가 API 개발

 

* To-Do 추가 API
URL : http://localhost:8000/todo/create

입력필드
user_id (string) : 사용자 id
todo_id (int) : To-Do 고유 아이디
name (string) : To-Do 이름

출력필드
없음

 

To-Do를 추가하는 API는 조회랑 반대로 입력만 있고 출력이 없습니다. 

 

이쯤에서 Input/output에 대해 한 가지 알고 넘어가야 할 것이 있는데, 현재 추가 API는 출력 필드가 없습니다만, 그렇다고 서버에서 아무값도 내려주지 않는것은 아닙니다. 출력필드가 없다는 것은 단순히 통신 데이터에서 데이터 영역이 없다는 것을 뜻하며, 그 외에 HTTP 헤더 값은 전달됩니다.

 

실제로 요청이 들어오면 비어있는 응답을 주는 소스를 만들어봅시다.

 

 

# ~/todo/views.py
class Test(APIView):
    def post(self, request):
        return Response()
# ~/todo/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('test', views.Test.as_view(), name='test'),
]

 

요롷게 ~/todo/test로 호출하면 바로 Response()를 주는 코드를 만들었습니다. 그리고 postman이나 insomnia로 call 해보도록 합시다.

 

insomnia 콜

preview에 No body라고 나오지만 옆에 Header를 보면 7이라고 적혀있습니다. Header를 눌러봅시다.

 

Header정보가 들어있다

Header 뿐만 아니라 위에 200 OK라는 HTTP 상태 코드값도 받습니다. 보통 웹서비스들은 이 HTTP 상태 코드로 서비스 요청이 성공인지 실패인지 판단하게 됩니다. 

 

404 page not found나 400 bad request 등 익숙한 http 코드들이 있을 텐데요, 200은 성공이라는 코드입니다. 기타 코드 규칙이나 정보는 아래 url 참고.

 

HTTP 코드 - HTTP 상태 코드 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)

 

우리 소스에는 resturn Response()이라고 적혀있지만 Response()의 default 코드값은 200입니다. 이걸 만약에 400으로 주면 클라이언트에서 요청 실패로 인식합니다.

 

# ~/todo/views.py
class Test(APIView):
    def post(self, request):
        return Response(status=400)

400 Bad Request

 

위에 코드 정의처럼 HTTP 코드는 이미 통신 규약으로 성공코드와 에러코드가 정의되어있습니다. 이 코드값을 그대로 이용할 수도 있고, 사용자 정의 코드를 사용하는 곳도 있습니다. 실제로 서비스를 운영하다 보면 기존에 정의되어있지 않은 에러가 상당히 많을 수 있기 때문에 보통 큰 서비스를 하는 곳들은 자체적인 코드값을 상요하여 성공/실패를 구분합니다. 

 

 

 

다시 To-Do 추가 API로 돌아와서, 3개의 input값임 user_id, todo_id, name을 가지고 새로운 To-Do를 만들어주는 코드를 짜 봅시다.

 

# ~/todo/views.py
class TaskCreate(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")
        todo_id = request.data.get('todo_id', "")
        name = request.data.get('name', "")

        Task.objects.create(id=todo_id, user_id=user_id, name=name)

        return Response()
# ~/todo/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('create', views.TaskCreate.as_view(), name='create'),
    url('select', views.TaskSelect.as_view(), name='select'),
    url('test', views.Test.as_view(), name='test'),
]

 

 

input을 그대로 받아서 Task.objects.create()로 생성해줍니다. 그리고 resturn Response(). urls.py에 create를 등록한 다음 화면에서 +를 누르고 추가할 To-Do 이름을 입력하고 엔터를 누릅니다.

 

추가가 잘 됩니다.

화면은 잘 확인했고, DB에도 잘 들어가는지 확인합니다.

 

DB도 확인

start_date는 자동으로 오늘, done은 자동으로 false로 입력됩니다. (models.py에서 default 설정해놓은 대로)

 

화면에서 나온다고 안심하지 말고 항상 서버 쪽도 체크해주는 습관을 가집시다 ㅋㅋ 가끔 화면에만 반영되고, DB에는 안 들어가는 경우도 있으니까요.

 

 

삭제 API 개발

 

* To-Do 삭제 API
URL : http://localhost:8000/todo/delete

입력필드
todo_id (int) : To-Do 고유 아이디

출력필드
없음

 

 

삭제 API는 다른 서비스에 비해 간단합니다. 삭제할 To-Do id를 올리면 DB에서 그 To-Do를 삭제하고, 성공 응답(200)을 주면 됩니다. 

 

지금은 이렇게 간단하게 만들지만, 나중에는 삭제하는 대상이 존재하는지, 삭제하는 대상이 현재 로그인 한 사용자의 To-Do 목록인지 등 이것저것 체크하는 로직이 들어가야 할 것입니다. 하지만 여기서는 그냥 id 올라오면 바로 삭제하도록 만들겠습니다.

 

# ~/todo/views.py

class TaskDelete(APIView):
    def post(self, request):
        todo_id = request.data.get('todo_id', "")
        task = Task.objects.get(id=todo_id)
        if task:
            task.delete()

        return Response()
# ~/todo/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('create', views.TaskCreate.as_view(), name='create'),
    url('delete', views.TaskDelete.as_view(), name='delete'),
    url('select', views.TaskSelect.as_view(), name='select'),
    url('test', views.Test.as_view(), name='test')
]

 

 

views.py와 urls.py에 추가하고, 화면에서 정상적으로 동작하는지 삭제 버튼(휴지통 버튼)을 눌러봅시다.

 

휴지통 선택
삭제되었음

 

화면에서 삭제된 것을 확인했으면, DB에서도 정상적으로 삭제되었는지 확인합니다.

 

DB 확인

 

삭제는 끝~!

 

 

완료 API

 

* To-Do 완료 API
URL : http://localhost:8000/todo/toggle

입력필드
todo_id (int) : To-Do 고유 아이디

출력필드
없음

 

완료 API는 To-Do를 완료했다고 체크하는 서비스입니다. I/O는 삭제와 비슷하지만 안에 로직만 삭제가 아니라 수정으로 바뀝니다.

 

# ~/todo/views.py
class TaskToggle(APIView):
    def post(self, request):
        todo_id = request.data.get('todo_id', "")
        task = Task.objects.get(id=todo_id)
        task.done = False if task.done else True
        task.save()
        return Response()
# ~/todo/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('create', views.TaskCreate.as_view(), name='create'),
    url('delete', views.TaskDelete.as_view(), name='delete'),
    url('select', views.TaskSelect.as_view(), name='select'),
    url('toggle', views.TaskToggle.as_view(), name='toggle'),
    url('test', views.Test.as_view(), name='test')
]

 

코드를 적용하고, 화면에서 모든 To-Do를 체크해봅시다.

 

체크체크
done 필드가 1(True)인것을 체크

 

 

네 이것으로 조회/완료/추가/삭제 API를 완성했습니다!

이제 [실습 2] 화면에서도 [예제 2]와 동일한 기능을 할 수 있습니다.

 

 

 

 

마치며

 

이번 포스팅에서는 To-Do CRUD 기능을 화면에 맞춰 서버 API를 만들었습니다. 하지만 이번 시간에 만든 API는 단순 기능 구현을 위한 코드로, 완성도가 높지 않은데요, 각종 예외처리나 오류처리, 성공 시 출력 데이터 등 상당히 손봐야 하는 부분이 많습니다.

 

예를 들어 To-Do를 추가하는 API에서 우리는 아무 응답 데이터도 주지 않았는데요, 실제로 새롭게 무언가를 추가하는 API에서는 추가된 데이터를 응답으로 내려주는 것을 클라이언트가 기대합니다.

 

예를 들어 "책 읽기"라는 To-Do를 만들었을 때, 클라이언트는 서버에서 DB에 저장하고, 새롭게 생성된 "책 읽기"라는 To-Do 객체를 받기를 원합니다. 왜일까요?

 

 

지금은 제가 만든 화면은 서버에 데이터를 추가함과 동시에 화면에서 따로 관리하는 To-Do 목록에도 같은 값을 추가합니다. 즉 여러분이 To-Do를 만든다면 서버에도 새로운 To-Do를 저장하고, 클라이언트(보통은 로컬이란 표현을 씁니다.)에서도 새로운 To-Do를 저장합니다. 즉 To-Do 목록을 이중 관리하는 것이지요. 이렇게 하는 것도 방법이긴 하지만 동기화가 제대로 안돼서 서버에는 없는 값이 화면에서 보이는 경우나 혹은 그 반대의 경우가 생길 수 있습니다. 따라서 서버에서 To-Do를 생성할 때 정말 제대로 생성이 되었는지와 생성되었을 경우 생성된 To-Do를 클라이언트로 내려준다면, 클라이언트에서 그 데이터를 확인하고 자체 관리하는 리스트와 비교하거나 새로 추가할 수 있게 됩니다.

 

위에서 언급한 작업 외에도 API에 공통적으로 들어가야 하는 입력 파라미터 체크나 에러코드 정의 등을 다음 시간에 추가해보도록 하겠습니다. 

 

 

 

 

 

 

 

 

 

 

 

반응형

댓글

Designed by JB FACTORY