REST API Server 개발 

REST : Representational State Transfer (RESTful)

API : Application Programming Interface

 

"Representational State Transfer(REST)는 API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍처입니다. REST는 처음에 인터넷과 같은 복잡한 네트워크에서 통신을 관리하기 위한 지침으로 만들어졌습니다. REST 기반 아키텍처를 사용하여 대규모의 고성능 통신을 안정적으로 지원할 수 있습니다. 쉽게 구현하고 수정할 수 있어 모든 API 시스템을 파악하고 여러 플랫폼에서 사용할 수 있습니다. API 개발자는 여러 아키텍처를 사용하여 API를 설계할 수 있습니다. REST 아키텍처 스타일을 따르는 API를 REST API라고 합니다. REST 아키텍처를 구현하는 웹 서비스를 RESTful 웹 서비스라고 합니다. RESTful API라는 용어는 일반적으로 RESTful 웹 API를 나타냅니다. 하지만 REST API와 RESTful API라는 용어는 같은 의미로 사용할 수 있습니다."

https://aws.amazon.com/ko/what-is/restful-api/

 

RESTful API란 무엇인가요? - RESTful API 설명 - AWS

Amazon API Gateway는 어떤 규모에서든 개발자가 API를 손쉽게 생성, 게시, 유지 관리, 모니터링 및 보안 유지할 수 있도록 하는 완전관리형 서비스입니다. API Gateway를 사용하면 실시간 양방향 통신 애

aws.amazon.com

 

https://meetup.nhncloud.com/posts/92

 

REST API 제대로 알고 사용하기 : NHN Cloud Meetup

REST API 제대로 알고 사용하기

meetup.nhncloud.com

 

API 서버 개발은 주로 프레임워크를 이용해 개발을 진행한다.  
그 중 flask(Python library) 프레임워크를 사용해 개발을 진행해 보자. . 

 

Framework 프레임워크

프레임워크 (naver.com) 프레임워크 소프트웨어 어플리케이션이나 솔루션의 개발을 수월하게 하기 위해 소프트웨어의 구체적 기능들에 해당하는 부분의 설계와 구현을 재사용 가능하도록 협업화

bpdev.tistory.com

 

Flask Framework

 

Quickstart — Flask Documentation (2.3.x)

Quickstart Eager to get started? This page gives a good introduction to Flask. Follow Installation to set up a project and install Flask first. A Minimal Application A minimal Flask application looks something like this: from flask import Flask app = Flask

flask.palletsprojects.com

$ pip install Flask

로 설치한 후 

 

from flask import Flask

app = Flask(__name__)

if __name__ == '__main__' : 
    app.run()

이 네 줄이면 플라스크로 서버를 돌릴 수 있다! 위 링크에서 자세한 설명을 볼 수 있음.   
플라스크 서버를 실행시켜서 설정한 목적에 맞는 서비스를 개발해 보자. 

 

app.py

# conda create -n lambda_app python=3.10
# conda env remove -n lambda_app
# lambda_app 가상환경에서 작업. 

from flask import Flask
from flask_restful import Api
from resources.recipe import RecipeListResource, RecipeResource 

app = Flask(__name__)
api = Api(app)
# app을 플라스크 api에 넣어서 사용하겠어. 

# 경로(path)와 API동작코드(resource)를 연결한다. flask의 문법대로. flask에게 알려주기. 
api.add_resource( RecipeListResource, '/recipes' )
api.add_resource( RecipeResource, '/recipes/<int:recipe_id_abc>' )

if __name__ == '__main__' : 
    app.run()

 

 

순서가 중요하다.

작업 진행 방식이 중요하다. 
절차에 따라 필요하면 만든다. 
필요해졌을 때 만드는 것이 좋다. 


1. 프로젝트 기획서, 화면 기획서 등을 보고  
2. 먼저 DB테이블을 설계하고 
3. 프로젝트용 서버가 db에 접속할 수 있도록 workbench에서 별도 db 접속 계정을 만들어준다. 아이디, 비번 설정. 
4. 연결할 때가 되었으니 설계한 recipe_db schema 생성하기 

5. 프로젝트용 서버가 해당 db schema에 접속할 수 있도록 권한을 설정해준다. 

- 서버의 새로운 프로젝트 전용 DB접속을 위한 id pw를 만들어 준다. 

  • DB admin으로 접속하여, 프로젝트 API 서버가 DB에 접속할 때 사용할 id pw 설정해 주기.
  • HOST name은 주 계정의 것을 사용한다. 
  • use mysql; 실행
  • create user '계정이름'@'%' identified by '비번'; 실행
    -  % 어디서나 접속할 수 있게. 네트워크라는 뜻. 
    -  비번은 문자숫자만 됨 
  • 권한설정 : grant ALL privileges on DB이름.* to '계정이름'@'%'; 실행

6. recipe_db schema에 2번에서 설계한 테이블들 생성해주기 

 

7. API 설계 

API 명세서 : 레시피 등록 API

Method URL Request Response API Description
POST http://127.0.0.1:5000/recipes POST http://127.0.0.1:5000/recipes
● def post(self)
 
body : data(JSON)

{
    "name": "된장찌게",
    "description": "맛있게 끓이는 방법",
    "num_of_servings": 2,
    "cook_time": 30,
    "directions": "된장이랑 고기 볶고 물 붓고 호박넣고 두부넣고 끓임",
    "is_publish": 0
}
result : success / fail 새로운 레시피 정보 입력
API 
GET http://127.0.0.1:5000/recipes GET http://127.0.0.1:5000/recipes
● def get(self)
[{
    "name": "된장찌게",
    "description": "맛있게 끓이는 방법",
    "num_of_servings": 2,
    "cook_time": 30,
    "directions": "된장이랑 고기 볶고 물 붓고 호박넣고 두부넣고 끓임",
    "is_publish": 0
} {} {} ...
저장된 레시피 데이터들 조회 API
GET http://127.0.0.1:5000/recipes/2 (레시피아이디) GET http://127.0.0.1:5000/recipes/2 (레시피아이디)
● def get(self, 입력받은 변수 2)
{
    "name": "된장찌게",
    "description": "맛있게 끓이는 방법",
    "num_of_servings": 2,
    "cook_time": 30,
    "directions": "된장이랑 고기 볶고 물 붓고 호박넣고 두부넣고 끓임",
    "is_publish": 0
}
레시피 1개 조회 API
PUT http://127.0.0.1:5000/recipes/2 PUT http://127.0.0.1:5000/recipes/2
● put(self, 입력받은 변수 2)
 
body : data(JSON)

{
    "name": "된장찌게",
    "description": "맛있게 끓이는 방법",
    "num_of_servings": 2,
    "cook_time": 30,
    "directions": "된장이랑 고기 볶고 물 붓고 호박넣고 두부넣고 끓임",
    "is_publish": 0
}
result : success / fail 레시피 수정
API
DELETE http://127.0.0.1:5000/recipes/2 DELETE http://127.0.0.1:5000/recipes/2
● def delete(self, 입력받은 변수 2)
result : success / fail 레시피 삭제
API

 

https://www.oracle.com/kr/database/what-is-json/

 

JSON이란 무엇인가?

JSON을 이용하는 프로그래머, 개발자, IT 전문가들은 어떤 언어에서든 데이터 구조와 실제 데이터를 다른 언어 및 플랫폼에서 해석 가능한 형식으로 전송할 수 있습니다.

www.oracle.com

 

URI와 URL, PATH 등에 대한 설명

https://www.elancer.co.kr/blog/view?seq=74

 

URI와 URL, 어떤 차이점이 있나요? | 이랜서 블로그

uri와 url, 비슷한 듯 다른 it 용어, 어떤 차이점이 있는지 확인하기 | uri url 차이, uri 뜻, uri 란, uri url urn, uri vs url

www.elancer.co.kr

 

궁금해서 데려옴 (아직 다 이해가 안 가지만 필요할 거 같다는) 

https://bcho.tistory.com/955

 

REST API의 이해와 설계-#3 API 보안

REST API의 이해와 설계 #3 API 보안 REST API 보안 API 보안에 대해서는 백번,천번을 강조해도 과함이 없다. 근래에 대부분의 서비스 시스템들은 API를 기반으로 통신을 한다.앱과 서버간의 통신 또는

bcho.tistory.com

 


* get 메소드는 body를 통해 서버에 요청을 보내지 않는다. 
: get 메소드는 서버에서 데이터를 query string으로 불러옴.  

Qurry strimg
사용자가 입력 데이터를 전달하는 방법중의 하나로, url 주소에 미리 협의된 데이터를 파라미터를 통해 넘기는 것을 말한다.

http://host:port/path?querystring
query parameters( 물음표 뒤에 = 로 연결된 key value pair 부분)을 url 뒤에 덧붙여서 추가적인 정보를 서버 측에 전달하는 것이다. 클라이언트가 어떤 특정 리소스에 접근하고 싶어하는지 정보를 담는다.

형식
정해진 엔드포인트 주소 이후에 ?를 쓰는것으로 쿼리스트링이 시작함을 알린다.
parameter=value로 필요한 파라미터의 값을 적는다.
파라미터가 여러개일 경우 &를 붙여 여러개의 파라미터를 넘길 수 있다.
엔드포인트주소/엔드포인트주소?파라미터=값&파라미터=값
= 로 key 와 value 가 구분된다.

출처 : https://velog.io/@pear/Query-String-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81%EC%9D%B4%EB%9E%80

8. POSTMAN 

https://www.postman.com/downloads/

 

Download Postman | Get Started for Free

Try Postman for free! Join 25 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.

www.postman.com

 

'포스트맨은 API를 구축하고 사용하기 위한 API 플랫폼입니다.

Postman은 API 라이프사이클의 각 단계를 간소화하고 협업을 간소화하여 더 나은 API를 더 빠르게 만들 수 있습니다.'

- Collection 콜렉션 만들기 
- Add Request : 기능별(POST, GET, PUT, DELETE)로 이름 하나씩. 줍니다.  
- JSON은 무조건 큰따옴표 

- 포스트맨에서 SEND 누르면 SERVER에 REQUEST 해 준다. 

 

 


9. VSCode 프로젝트 실행할 가상환경에서 메인파일 app.py과 서브 파일 recipe.py를 작성한다. 
app.py
from flask import Flask
from flask_restful import Api
from resources.recipe import RecipeListResource 

recipe.py
from flask_restful import Resource
from flask import request

 

 


10. 파이썬으로 MySQL 연결하는 법

https://dev.mysql.com/doc/connector-python/en/connector-python-example-connecting.html

 

MySQL :: MySQL Connector/Python Developer Guide :: 5.1 Connecting to MySQL Using Connector/Python

5.1 Connecting to MySQL Using Connector/Python The connect() constructor creates a connection to the MySQL server and returns a MySQLConnection object. The following example shows how to connect to the MySQL server: import mysql.connector cnx = mysql.conn

dev.mysql.com

 

use the connect() method 
- $ pip install mysql-connector-python 으로 설치하고 import 

 

mysql_connection.py

import mysql.connector
 config.py 파일을 app.py 경로에 만들어서 서버의 db 해당 schema에 접근할 수 있도록 허가해 준다.

② config.py 보안 상 노출하지 않는 것이 좋은 파일이므로 .gitignore 파일에 추가해 준다.
 mysql_connection.py 파일을 app.py 경로에 만들어서 데이터베이스 연결 전용 파일로 사용한다.

# 데이터베이스 연결 전용 파일

import mysql.connector
from config import Config

def get_connection() : 
    connection = mysql.connector.connect(
        host = Config.HOST, 
        database = Config.DATABASE, 
        user = Config.DB_USER,
        password = Config.DB_PASSWORD
    )
    return connection

 

유닛테스트가 중요하다. 
문제가 있는 곳을 유추하려면 
단계마다 테스트를 해서 반드시 ! 

괜찮은지 확인하고 넘어가야 한다. 

디버깅, 문제해결능력 등 중요. 

오탈자 띄어쓰기 부호 체크

 

REST API 

Resource : url http://server.com/users
Method : get/post/delete/put.....
Message : JSON, XML 

 

이런 형식이 JSON이다. 

{
	"name":"김치찌게", 
	"description":"맛있게 끓이는 방법', 
	"num_of_serving": 4, 
	"cook_time": 30,
	"directions": "고기 볶고 김치 넣고 물 붓고 두부 넣고 끓임",
	"is_publish": 1
}

 

튜플 기본 왜 잊었어????

기본은 잊으면 안 된다. 
(10) ☞ 10
(10, ) ☞ (10)

if 문에서는 비 정상적인 때를 먼저 체크하는 방식으로 코드를 짜도록 한다. 

 

 

resourses/resipe.py

app.py 파일이 있는 경로에 resourses 폴더를 만들고 그 안에 resipe.py 파일도 만들어준다. 

# api 동작하게 만드는 메소드
from flask_restful import Resource
from flask import request

# mysql 서버와 연결해주는 메소드
import mysql.connector
from mysql.connector import Error
from mysql_connection import get_connection


# API 동작하는 코드를 만들기 위해서는 
# class를 만들어야 한다. 

# class란 ???? 일단 비슷한 데이터끼리 모아놓은 것으로 이해하자 (테이블이랑 비슷한 개념)
# 클래스는 변수와 함수로 구성된 묶음 
# 테이블과 다른 점 : 함수가 있다. 


# API를 만들기 위해서는 flask_restful 라이브러리의 Resource 클래스를 상속받아서 만들어야 한다. 파이썬에서 상속은 괄호! 
# 먼저 만들어보다가 추상적인 개념을 설명해 주신다 함
# class 클래스이름(상속받는애): 변수+함수
# 프레임워크로 제공되는 함수 def post():
 


# 경로가 다르면 새로운 클래스 생성해야 함 
class RecipeResource(Resource) : 
    # GET 메소드에서 경로로 넘어오는 변수는 GET함수의 파라미터로 적어주면 된다. 
   
    def get(self, recipe_id_abc) : 
        # 1. 클라이언트로부터 데이터를 받아온다. 
        # 위의 recipe_id_abc에 담겨있다. 
        print(recipe_id_abc)

        # 2. 데이터베이스에 레시피 아이디록 쿼리한다. (데려오라고.)
        try : 
      		# get_connection() 쓸거고
            connection = get_connection()
			# 2-1. MySQL workbench에서 실행해본 쿼리문 데려오고
			query = '''select * from recipe
                    where id = %s;'''
            # 2-2. 클라이언트로부터 받아온 데이터값 튜플 형태로 변환하기로 했다고 함
            record = (recipe_id_abc, )
            # 2-3. get_connection() 커넥션 객체로부터 cursor()메소드를 호출하여 Cursor 객체를 가져온다. 
            cursor = connection.cursor(dictionary=True) # JSON 형식으로 불러오려면 dictionary true 
            # 2-4. 쿼리문을 커서로 실행한다. 
            cursor.execute(query, record)
            # fetch all ()은 cursor의 목록을 모두 리스트로 가져옴
            # cursor 객체의 fetchall() 메소드를 사용해서 데이터를 서버로부터 가져온 후 fetch된 데이터를 사용한다.
            result_list = cursor.fetchall()
			# 확인확인 
			print(result_list)
			# 닫자닫자 db연결닫기
            cursor.close()
            connection.close()

        except Error as e : 
        	# 에러나면 클라이언트에게 에러라고 알려주
            print(e) # 작업자한테도 
            return {'result':'fail', 'error':str(e)}, 500 # 디버깅, 터미널 확인, 포스트맨 확인. 

        # 3. 결과를 보내 클라이언트에 응답한다. 
        # 데이터가공이 필요하면, 가공한 후에 클라이언트에 응답한다. 
        i = 0
        for row in result_list :
            result_list[i]['created_at']= row['created_at'].isoformat()
            result_list[i]['updated_at']= row['updated_at'].isoformat()
            i = i + 1
            
        if len(result_list) != 1 : 
            return 
        else : 
            return {'result':'success', 'item':result_list[0]}

    def put(self, recipe_id_abc) :

        # 1. 클라이언트로부터 데이터 받아온다.
        print(recipe_id_abc)

        # body에 있는 json 데이터를 받아온다. 
        data = request.get_json()

        # 2.데이터베이스에 update한다. 
        try : 
            connection = get_connection()
            # make query 컬럼과 매칭되는 정보는 %s 쓸 수 있다. 
            query = '''update recipe 
                    set name = %s, description = %s, num_of_servings = %s, cook_time = %s, directions = %s, is_publish = %s
                    where id = %s; '''
            record = ( data['name'], data['description'], data['num_of_servings'], data['cook_time'], data['directions'], data['is_publish'], recipe_id_abc)
            cursor = connection.cursor()
            cursor.execute(query, record)
            connection.commit()
            cursor.close()
            connection.close()

        except Error as e : 
            print(e)
            return {'result':'fail', 'error':str(0)}, 500

        return {'result':'success'}


    def delete(self, recipe_id_abc) : 
        # 1. 클라이언트로부터 데이터 받아온다. 
        print(recipe_id_abc)

        # 2. DB에서 삭제한다. 
        try : 
            connection = get_connection()
            query = '''delete from recipe where id = %s;'''
            record = (recipe_id_abc, )
            cursor = connection.cursor()
            cursor.execute(query, record)
            connection.commit()
            cursor.close()
            connection.close()
        except Error as e : 
            print(e)
            return {'result':'success', 'error':str(e)}, 500

        # 3. 결과를 리턴해준다. 
        return {'result':'success'}


class RecipeListResource(Resource) : 
    def post(self) : 
        # 포스트로 요청한 것을 처리하는 코드 작성을 우리가 한다!! class 함수에서는 무조건 self 써줘야 함. 함수 소속이라는 뜻 . 
        
        # 로직을 짠다. 무언가가 처리되는 순서를 짜는 것. 
        # 데이터 한 세트 복사해서 형식 보이게 해 두고.. 
            # {
            #     "name": "김치찌게",
            #     "description": "맛있게 끓이는 방법",
            #     "num_of_serving": 4,
            #     "cook_time": 30,
            #     "directions": "고기 볶고 김치 넣고 물 붓고 두부 넣고 끓임",
            #     "is_publish": 1
            # }
        
        # 1. 클라이언트가 보낸 데이터를 받아온다. 
        data = request.get_json()
        print(data)

        # 2. DB에 저장한다. 
        try : 
            # 2-1. 데이터베이스를 연결한다. 
            connection = get_connection()
            # 2-2. 쿼리문 만든다. ############## 중요!!!! 컬럼과 매칭되는 데이터만 %s로 바꿔준다. 
            query = '''insert into recipe 
                    (name, description, num_of_servings, cook_time, directions, is_publish)
                    values
                    (%s, %s, %s, %s, %s, %s);'''
            # 2-3. 쿼리에 매칭되는 변수 처리!  중요! 튜플로 처리해준다! 컬럼명 변수 명 같은 정보 이름 통일 중요
            record = ( data['name'], data['description'], data['num_of_servings'], data['cook_time'], data['directions'], data['is_publish'])
            # 2-4. 커서를 가져온다. 
            cursor = connection.cursor()
            # 2-5. 쿼리문을 커서로 실행한다.
            cursor.execute(query, record) 
            # 2-6. DB에 반영 완료하라는 commit 해줘야 한다. (데이터를 확정 갱신)
            connection.commit()
            # 2-7. 자원해제
            cursor.close()
            connection.close()

        except Error as e : 
            print(e)
            return{'result':'fail', 'error':str(e)}, 500
            # internal server error

        # 3. 에러 났으면 에러났다고 알려주고, 그렇지 않으면, 잘 저장되었다고 알려준다. (return)

        # 필요에 따라 추가하자. 
        return{'result':'success'}
    
    def get(self):
        print("레시피 가져오는 API 동작했음")
        # 로직을 짜자 1,2,3

        # 1. 클라이언트로부터 데이터를 받아온다. 

        # 2. 저장된 레시피 리스트를 DB로부터 가져온다.
        # 2-1. DB 커넥션 
        try : 
            connection = get_connection()
            # 2-2. 쿼리문 작성
            query = '''select * from recipe
                        order by created_at desc;'''
            # 2-3. 커서 가져온다. 
            cursor = connection.cursor(dictionary=True)
            # 2-4. 쿼리문을 커서로 실행한다. 
            cursor.execute(query)
            # 2-5. 실행 결과를 가져온다. 
            result_list = cursor.fetchall()
            # 디버깅할 유닛 테스트 확인확인
            print(result_list) # 프린트하면 튜플로 나온다. 테이블 / JSON 형태로 바꿔져야됨.  
            cursor.close()
            connection.close()

            # 3. 데이터가공이 필요하면, 가공한 후에 클라이언트에 응답한다. 
            i = 0
            for row in result_list :
                result_list[i]['created_at']= row['created_at'].isoformat()
                result_list[i]['updated_at']= row['updated_at'].isoformat()
                i = i + 1

            return {'result':'success', 'count':len(result_list), 'items': result_list}

            return {"result":"success", "count":3}, 400
            # , 400은 상태코드 설정. 

        except Error as e :
            print(e)
            return {'result':'fail', 'error':str(e)}, 500

+ Recent posts