5부. 처음부터 끝까지 CRUD 만들어 보기 (2)

JeungJoo Lee
CrocusEnergy
Published in
37 min readOct 27, 2019

지난 편 ▶︎ 4부. 처음부터 끝까지 CRUD 만들어 보기 (1)

이번 Chapter에서는 유용한 Serverless 플러그인들을 설명하고 설치해보도록 하겠다.

Serverless 플러그인 설명 및 설치

serverless-mysql

이 플러그인은 Serverless 환경에서 커넥션 풀을 효율적으로 관리할 수 있는 MySQL Connection 라이브러리라고 보면 될것이다. 이것을 쓰는 가장 큰 이유는 Github 에서 잘 설명해주고 있다.

람다 함수의 Instance 들이 각각 요청된 유저에 의해 늘어났을 때 Connection Pool에 대한 자원을 계속 쓰기 때문에 나중엔 더이상 Connection 을 맺을 수 없는 상황까지 발생하기 때문에 이러한 부분을 효율적으로 관리하고자 이 라이브러리를 선택하였다. 일단 설치 해보자~

npm install --save serverless-mysql

serverless-aws-alias

이 플러그인은 Serverless Deploy 시 alias 옵션을 주어 dev (개발) 에 반영할 것인지 운영 (prod) 에 반영할 것인지 별칭을 람다에게 만들어 관리하게 해주는 핵심적인 라이브러리다. 가장 강추 하는 라이브러리로 꼭 추가를 바란다! 자세한 설명은 Deploy 시 설명하도록 하겠다.

npm install --save-dev serverless-aws-alias // 또는sls plugin install --name serverless-aws-alias

serverless-domain-manager

이 플러그인은 Route53에서 등록된 도메인의 레코드 생성과 yml 설정안에 옵션으로 Cloud Front 를 자동으로 생성 해주는 고마운 놈이다. 요놈도 설치 해 주자

npm install --save-dev serverless-domain-manager // 또는sls plugin install --name serverless-domain-manager

serverless-offline

이 플러그인은 람다를 배포하기 전에 로컬 서버를 띄어 yml 에 정의된 API 대로 람다를 테스트할 수 있는 좋은 플러그인이다.

npm install --save-dev serverless-offline // 또는sls plugin install --name serverless-offline

serverless-webpack

Webpack은 배포시 자동으로 Bundling 하고 코드의 Optimization 을 위해 사용하도록 한다.

npm install --save-dev serverless-webpack webpack-node-externals

설치가 완료 되었다면 sample 프로젝트 밑에 webpack.config.js 설정 파일을 만들어 아래와 같이 입력하고 저장한다.

const slsw = require('serverless-webpack')
const nodeExternals = require('webpack-node-externals')
module.exports = {
entry: slsw.lib.entries,
target: 'node',
devtool: 'source-map',
externals: [nodeExternals()],
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
optimization: {
minimize: false,
},
performance: {
hints: false,
},
module: {
rules: [
{
test: /\\.js$/,
loader: 'babel-loader',
include: __dirname,
exclude: /node_modules/,
},
],
},
}

자 이제 개발을 위한 모든 라이브러리와 Serverless 의 플러그인은 설치가 완료되었다. 다음 편에선 테스트 코드를 시작으로 RestFul Service를 만들어 보자!

테스트 코드 작성하기

테스트 코드를 작성 해 보겠다. 먼저 DB 와 Access 하여 DML 을 수행할 수 있는 DAO 를 구현해보겠다.

DAO 를 구성하기 위해선 DB와의 커넥션을 맺고 Query를 날릴 수 있는 기능이 필요하다. 우선 아래와 같이 test/utils/db 디렉토리에 Javascript 파일을 만든다.

코드는 아래와 같다.

const dbClient = require('serverless-mysql')({
config: {
host: 'localhost',
user: 'kstarlive',
password: '!kstarlive!',
port: 3306,
database: 'kstarlive',
},
})
test('DB Connection 테스트', () => {
expect(typeof dbClient === 'object').toBe(true)
})
test('Query 테스트', async () => {
await dbClient.connect()
const result = await dbClient.query(` SELECT 2*4 AS number `)
expect(result[0].number).toBe(8)
await dbClient.end()
})

DB Configuration 을 통한 Client 추출과 커넥션 후 간단한 쿼리 테스트 코드이다. 테스트를 해보면 아래와 같이 성공한다. (단, DB의 경우 Amazon RDS 의 VPC 설정이 되어있다면 Bastion 을 통해 Port Fowarding 으로 localhost 3306 으로 접근한다. )

코드는 아래와 같다.

const dbClient = require('serverless-mysql')({
config: {
host: 'localhost',
user: 'kstarlive',
password: '!kstarlive!',
port: 3306,
database: 'kstarlive',
},
})
test('DB Connection 테스트', () => {
expect(typeof dbClient === 'object').toBe(true)
})
test('Query 테스트', async () => {
await dbClient.connect()
const result = await dbClient.query(` SELECT 2*4 AS number `)
expect(result[0].number).toBe(8)
await dbClient.end()
})

DB Configuration 을 통한 Client 추출과 커넥션 후 간단한 쿼리 테스트 코드이다. 테스트를 해보면 아래와 같이 성공한다. (단, DB의 경우 Amazon RDS 의 VPC 설정이 되어있다면 Bastion 을 통해 Port Fowarding 으로 localhost 3306 으로 접근한다. )

테스트가 완료 되었다면 해당 기능을 실제 src/utils/db 에 DBManager.js 파일로 옮겨보도록 하겠다.

import env from '/../../config/env.local'// Jest 테스트시 process.env 설정에 env.local.json 파일을 읽게 하기 위해 설정한 부분임 
if(process.env.MODE !== 'Serverless') {
process.env = Object.assign(process.env, {
RDS_HOSTNAME : env.database.rds_hostname,
RDS_USERNAME : env.database.rds_username,
RDS_PASSWORD : env.database.rds_password,
RDS_PORT : env.database.rds_port,
RDS_DATABASE : env.database.rds_database
})
}
// DBClient 개체
export const dbClient = require('serverless-mysql')({
config: {
host : process.env.RDS_HOSTNAME,
user : process.env.RDS_USERNAME,
password: process.env.RDS_PASSWORD,
port : process.env.RDS_PORT,
database: process.env.RDS_DATABASE
}
})
// DB Connect 이후 Query를 실행 하는 함수
export const executeQuery = async (_query, params) => {
try {
await dbClient.connect()
return await dbClient.query(_query, params)
} catch (err) {
throw err
} finally {
await dbClient.end()
}
}

여기서 env.local.js 의 파일은 DB 정보를 환경 변수 운영 / 개발 / 로컬 환경에 따라 다르게 Setting 해 주기 위해 설정하는 부분이다.

테스트 코드를 아래와 같이 다시 변경 후 테스트 해보도록 하겠다.

import {dbClient, executeQuery} from "../../../src/utils/db/DBManager"test('DB Connection 테스트', () => {
expect(typeof dbClient === 'object').toBe(true)
})
test('Query 테스트', async () => {
const result = await executeQuery(`SELECT 2*4 AS number`)
expect(result[0].number).toBe(8)
})

동일하게 테스트가 통과되었다.

이제 executeQuery 함수를 만들었으니 DAO를 작성해 보도록 하자. test/sample/dao 위치에 sample.dao.test.js 파일을 만들자.

readAllItems (아이템 모두 조회하기)

import { executeQuery } from '../../../src/utils/db/DBManager'test('readAllItems 테스트', async () => {
try {
const result = await executeQuery(`SELECT * FROM test`)
expect(result.length).toBeGreaterThanOrEqual(0)
} catch (err) {
console.error(err)
}
})

위와 같이 test 테이블에 아이템을 모두 조회하는 기능을 테스트한다고 하자. 위와 같이 초반엔 테스트 코드를 만들 수 있다. readAllItems 기능을 조금 더 구조화해서 만들어 본다고 하면 아래와 같은 파일 구조로 구조화 해볼 수 있다.

readAllItems 라는 함수를 하나 만들었다. 이제 다시 테스트 코드로 돌아와서 코드를 변경해 보도록 하겠다.

// 테스트 코드 변경 후
import { readAllItems } from '../../../src/sample/dao/itemDAO'
test('readAllItems 테스트', async () => {
const result = await readAllItems()
expect(result.length).toBeGreaterThanOrEqual(0)
})

위와 같이 RestFul Service에 들어갈 CRUD 을 구성하는 모든 기능을 코드로 옮겨 보도록하겠다.

/**
* src/sample/dao/itemDAO.js
**/
import { executeQuery } from '../../utils/db/DBManager'
import {
deleteItemQuery,
selectAllQuery,
selectOneQueryWithParams,
createItemQuery,
updateItemQuery,
} from '../../sql/query'
export const readAllItems = async () => {
try {
return await executeQuery(selectAllQuery)
} catch (err) {
console.error(err)
return []
}
}
export const readOneItem = async param => {
try {
return await executeQuery(selectOneQueryWithParams, [param.id])
} catch (err) {
console.error(err)
return []
}
}
export const createItem = async param => {
try {
return (
(await executeQuery(createItemQuery, [param.name, param.memo]))
.affectedRows === 1
)
} catch (err) {
console.error(err)
return false
}
}
export const updateItem = async param => {
try {
return (
(await executeQuery(updateItemQuery, [
param.name,
param.memo,
param.id,
])).affectedRows === 1
)
} catch (err) {
console.error(err)
return false
}
}
export const deleteItem = async id => {
try {
return (await executeQuery(deleteItemQuery, [id])).message === ''
} catch (err) {
console.error(err)
return false
}
}

dao를 테스트하는 전체 코드는 아래와 같다.

/**
* test/sample/dao/sample.dao.test.js
**/
import {
createItem,
deleteItem,
readAllItems,
readOneItem,
updateItem,
} from '../../../src/sample/dao/itemDAO'
test('readAllItems 테스트', async () => {
const result = await readAllItems()
expect(result.length).toBeGreaterThanOrEqual(1)
})
test('createItem 테스트', async () => {
const result = await createItem({
name: `이정주 ${Math.random() * 10}`,
memo: `Test....`,
})
expect(result).toBe(true)
})
test('readOneItem 테스트', async () => {
const result = await readOneItem({ id: 1 })
expect(result[0].name).toBe('이정주')
})
test('updateItem 테스트', async () => {
const result = await updateItem({
id: 1,
name: '김개똥',
memo: '테스트업데이트',
})
expect(result).toBe(true)
})
test('deleteItem 테스트', async () => {
const result = await deleteItem(9)
expect(result).toBe(true)
})

DB에 테이블이 있다는 가정 하에 실행을 하면 아래와 같이 정상적으로 테스트가 Passed 가 되어 있을 것이다.

자 이제 람다 함수에서 RestFul API 에서 사용될 DAO 를 완성하였다. 이제 다음으로 실제 실행부인 람다 함수 핸들러 작성, serverless.yml 환경 변수 설정 및 AWS Cloud Formation을 통해 자동 생성되는 옵션들을 확인해 보도록 하겠다.

먼저 serverless.yml 의 옵션을 docs 에서 확인해 보도록 하자.

# serverless.ymlservice:
name: myService
awsKmsKeyArn: arn:aws:kms:us-east-1:XXXXXX:key/some-hash # Optional KMS key arn which will be used for encryption for all functions
frameworkVersion: '>=1.0.0 <2.0.0'provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'dev'} # Set the default stage used. Default is dev
region: ${opt:region, 'us-east-1'} # Overwrite the default region used. Default is us-east-1
stackName: custom-stack-name # Use a custom name for the CloudFormation stack
apiName: custom-api-name # Use a custom name for the API Gateway API
websocketsApiName: custom-websockets-api-name # Use a custom name for the websockets API
websocketsApiRouteSelectionExpression: $request.body.route # custom route selection expression
profile: production # The default profile to use with this service
memorySize: 512 # Overwrite the default memory size. Default is 1024
reservedConcurrency: 5 # optional, Overwrite the default reserved concurrency limit. By default, AWS uses account concurrency limit
timeout: 10 # The default is 6 seconds. Note: API Gateway current maximum is 30 seconds
logRetentionInDays: 14 # Set the default RetentionInDays for a CloudWatch LogGroup
deploymentBucket:
name: com.serverless.${self:provider.region}.deploys # Deployment bucket name. Default is generated by the framework
blockPublicAccess: true # Prevents public access via ACLs or bucket policies. Default is false
serverSideEncryption: true # to use server-side encryption
sseKMSKeyId: arn:aws:kms:us-east-1:xxxxxxxxxxxx:key/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa # when using server-side encryption
sseCustomerAlgorithim: AES256 # when using server-side encryption and custom keys
sseCustomerKey: string # when using server-side encryption and custom keys
sseCustomerKeyMD5: md5sum # when using server-side encryption and custom keys
tags: # Tags that will be added to each of the deployment resources
key1: value1
key2: value2
deploymentPrefix: serverless # The S3 prefix under which deployed artifacts should be stored. Default is serverless
role: arn:aws:iam::XXXXXX:role/role # Overwrite the default IAM role which is used for all functions
cfnRole: arn:aws:iam::XXXXXX:role/role # ARN of an IAM role for CloudFormation service. If specified, CloudFormation uses the role's credentials
versionFunctions: false # Optional function versioning
.... 생략 .....

serverless.xml 에 굉장히 많은 옵션들이 존재한다.

이것을 일일이 살펴 볼 순 없고 현재 우리가 개발하고자 하는 Sample 프로젝트의 RestFul API 에서 사용 될 설정만 살펴보고 넘어가도록 하자. 해당 옵션은 주석으로 아래와 같이 설명하였다.

sample/serverless.xml

app: kstarlive # 이 부분은 Serverless Dashboard App 명과 일치해야 한다.
service: kstarlive-sample # Dashboard App 의 서비스 섹션에 생기는 App 보다 작은 단위다.
org: day34 # Serverless 계정에 생성된 Org 와 Mapping 된다.
provider:
name: aws # aws, azure 등 Vendor 지정
runtime: nodejs10.x # 사용 언어
stage: dev # stage 명 ( API Gateway Stage 와 헤깔리지 마세용 )
region: ap-northeast-2 # ap-northeast-1 (도쿄), ap-northeast-2 (서울)
stackName: kstarlive-sample # CloudFormation 에 생성될 Stack Name
apiName: kstarlive-sample # API Gateway 에 생성될 API Name
environment: # 람다에서 사용될 공통 환경변수 아래 custom 으로 설정되어 있음
RDS_HOSTNAME: ${self:custom.env.database.rds_hostname}
RDS_USERNAME: ${self:custom.env.database.rds_username}
RDS_PASSWORD: ${self:custom.env.database.rds_password}
RDS_PORT: ${self:custom.env.database.rds_port}
RDS_DATABASE: ${self:custom.env.database.rds_database}
MODE: 'Serverless' # Jest 테스트 시 환경변수를 custom 이 아닌 직접 configuration 해주기 위한 변수
role: arn:aws:iam::878392923027:role/service-role/mysql_usage # 람다 함수 실행 Role
vpc: # VPC 그룹
securityGroupIds: # 보안 그룹 ID
- sg-03d21453c7b42d0c6
- sg-049fd41a232c36553
subnetIds: # 서브넷 ID
- subnet-0f1ec8b88844fe72d
- subnet-07a2a9768d54219d3
functions: # 람다 함수 리스트
readAll: # 람다 함수 readAll
name: kstarlive-sample-readAllItems # 람다 함수명
handler: src/sample/handler.readAll # 람다 함수 위치
description: Retrieve items all. # 람다 함수 설명
events:
- http: # API Gateway
path: items # URI Path
method: get # HTTP Method
cors: true # CORS Enable
readItem:
name: kstarlive-sample-readItem
handler: src/func1/handler.readItem
description: Retrieve an item.
events:
- http:
path: item/{itemId}
method: get
cors: true
createOneItem:
name: kstarlive-sample-insertItem
handler: src/func1/handler.createOneItem
description: Insert an item
events:
- http:
path: item
method: post
cors: true
updateOneItem:
name: kstarlive-sample-updateItem
handler: src/func1/handler.updateOneItem
description: Update an item
events:
- http:
path: item
method: put
cors: true
deleteOneItem:
name: kstarlive-sample-deleteItem
handler: src/func1/handler.deleteOneItem
description: Delete an item
events:
- http:
path: item/{itemId}
method: delete
cors: true
custom: # Custom 옵션
stages:
- dev
env: ${file(../config/env.${opt:alias,'dev'}.json)} # CLI 입력시 --alias `alias 명` 에 따라 config 파일을 다르게 불러옴. 불러운 변수는 위의 provider.environment 에서 쓰
webpack: # Webpack 설정파일 Path
webpackConfig: ./webpack.config.js
includeModules: true
customDomain: # CloudFront 인스턴스 생성 및 Route53 도메인 레코드 생성시 필요한 부분
domainName: ${self:custom.env.domain.name} # 도메인명
stage: ${opt:alias,'dev'}
basePath: api # /api 기본 Path 로 설정함
certificateName: ${self:custom.env.domain.cert} # ACM 에서 생성한 SSL 인증서
createRoute53Record: true # Route53 레코드 생성여부.
endpointType: 'edge' # regional or edge 리전별 캐싱 전략에 대한 부분 선택할 수 있음
securityPolicy: tls_1_2 # TLS policy 정책
package:
individually: true
plugins: # 설치한 Serverless 공식 플러그인 리스트
- serverless-offline임
- serverless-aws-alias
- serverless-webpack
- serverless-domain-manager

각 항목 별로 주석은 달아 놓았으니 확인하면 될것 같다. 이 전체의 일부분 중 짚고 넘어가야 할 부분들만 아래 설명하도록 하겠다.

Alias 별로 DB 정보 다르게 가져오기

이 부분은 람다 함수 하나를 공유해서 DB 를 개발(Dev) 와 운영(Prod)으로 나누어 서비스 할 수 있는 방식이다. 나중에 람다 함수를 Serverless Deploy 를 통해 배포를 할 텐데 이 배포가 개발(Dev) 배포 인지 운영(Prod) 배포 인지 구분할 필요가 있다. 이 때, 옵션 — alias 를 쓸 수 있는데.. 이 옵션에 따라 provider 에 설정된 environment 의 env 값들이 달라질 것이다.

environment: # 람다에서 사용될 공통 환경변수 아래 custom 으로 설정되어 있음
RDS_HOSTNAME: ${self:custom.env.database.rds_hostname}
RDS_USERNAME: ${self:custom.env.database.rds_username}
RDS_PASSWORD: ${self:custom.env.database.rds_password}
RDS_PORT: ${self:custom.env.database.rds_port}
RDS_DATABASE: ${self:custom.env.database.rds_database}
MODE: 'Serverless' # Jest 테스트 시 환경변수를 custom 이 아닌 직접 configuration 해주기 위한 변수
.... 중략 ....custom: # Custom 옵션
stages:
- dev
env: ${file(../config/env.${opt:alias,'dev'}.json)} # CLI 입력시 --alias `alias 명` 에 따라 config 파일을 다르게 불러옴. 불러운 변수는 위의 provider.environment 에서 쓰

연관된 옵션으로는 이 옵션을 보면 된다. serverless 에서는 값을 표현할 때는 ${} 을 쓴다. 그리고 값 내부에서 opt:{옵션명} 은 CLI 명령어를 입력했을 때 옵션 들의 값을 불러올 수 있다. 이 부분이 prod 또는 dev 로 나누어 개발과 운영 배포를 각각 진행할 수 있는 핵심 Key 가 된다.

위에 custom.env 설정을 보면 config 위치에 json 파일을 참조하게 되는데 이 json 파일을 만들어 보자.

sample/config

env.dev.json

{
"database": {
"rds_hostname": "DB개발접속주소",
"rds_username": "DB아이디",
"rds_password": "DB패스워드",
"rds_port": "3306",
"rds_database": "DB데이터베이스명"
},
"domain" : {
"name" : "dev-sample.kstarlive.com",
"cert" : "*.kstarlive.com"
}
}

env.local.json

{
"database": {
"rds_hostname": "DB로컬접속주소",
"rds_username": "DB아이디",
"rds_password": "DB패스워드",
"rds_port": "3306",
"rds_database": "DB데이터베이스명"
},
"domain" : {
"name" : "dev-sample.kstarlive.com",
"cert" : "*.kstarlive.com"
}
}

env.prod.json

{
"database": {
"rds_hostname": "DB운영접속주소",
"rds_username": "DB아이디",
"rds_password": "DB패스워드",
"rds_port": "3306",
"rds_database": "DB데이터베이스명"
},
"domain" : {
"name" : "sample.kstarlive.com",
"cert" : "*.kstarlive.com"
}
}

env.local.json 의 경우는 serverless-offline 플러그인에서 제공하는 로컬 서버를 띄워서 작업하기 때문에 필요한 케이스이다. 단, 저자의 경우 AWS RDS 를 사용하고 있고 이 부분은 VPC 보안 그룹으로 설정되어 있기 때문에 로컬 테스트를 위해선 Bastion 을 통해 접속해야 한다. 그 방법 중 하나가 Port Forwading 이다. Port Forwading 시 명령어는 Linux 기준으로 대략적으로 이렇다.

ssh -fnNT -L 3306:"타겟 DB 주소":3306 -i "pem파일" 계정@Bastion 서버 IP

Lambda 함수 정의

두 번째로 소개할 설정은 람다 함수 정의이다. 주석은 달았지만 짚고 넘어가야 될 부분은 바로 아래 설정 중 Bold 체로 친 부분이다.

functions: # 람다 함수 리스트
readAll: # 람다 함수 readAll
name: kstarlive-sample-readAllItems # 람다 함수명
**handler: src/sample/handler.readAll # 람다 함수 위치**
description: Retrieve items all. # 람다 함수 설명
events:
- http: # API Gateway
path: items # URI Path
method: get # HTTP Method
cors: true # CORS Enable

위의 위치는 정해져 있는 것은 아니다. 저자의 경우 src/sample 하위의 handler.js 파일을 만들어 readAll 이라는 function을 export 하고 있다. 아래 코드를 살펴보다.

sample/src/sample/handler.js

export const readAll = async (event) => {
return {
statusCode: 200,
headers: {
'x-custom-header': 'my custom header value',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
say : 'hello'
}),
}
}

위의 구조가 람다 함수의 가장 기본적인 구조이다. return 을 할 때 statusCode, headers, body 로 response 를 해주면 된다. Sync 처리를 위해 async/await 처리가 가능하다. 함수명은 위에서도 설명했지만 readAll 이다.

결론적으로, serverless.yml 에서 handler 에 정의된 src/sample/handler.readAll 에서 handler 는 파일명 readAll 은 export 하고 있는 함수 명이라고 보면 된다. 이렇게 정의된 하나의 function은 AWS Labmda 에 독립적으로 등록이 된다.

이제 RestFul API를 위한 람다 함수의 Full Code는 아래와 같다.

import {createItem, deleteItem, readAllItems, readOneItem, updateItem} from './dao/itemDAO'
import {
HTTP_STATUS_FAILED_MESSAGE,
HTTP_STATUS_SUCCESS_MESSAGE,
} from '../common/js/constants'
export const readAll = async () => {
const resultQuery = await readAllItems()
return {
statusCode: 200,
headers: {
'x-custom-header': 'my custom header value',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(resultQuery),
}
}
export const readItem = async event => {
const resultQuery = await readOneItem({ id: event.pathParameters.itemId })
return {
statusCode: 200,
headers: {
'x-custom-header': 'my custom header value',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(resultQuery),
}
}
export const createOneItem = async event => {
const { name, memo } = JSON.parse(event.body)
const resultQuery = await createItem({
name,
memo,
})
return {
statusCode: resultQuery ? 201 : 200,
headers: {
'x-custom-header': 'my custom header value',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
message: resultQuery
? HTTP_STATUS_SUCCESS_MESSAGE
: HTTP_STATUS_FAILED_MESSAGE,
success: resultQuery,
}),
}
}
export const updateOneItem = async event => {
const { id, name, memo } = JSON.parse(event.body)
const resultQuery = await updateItem({
name,
memo,
id,
})
return {
statusCode: 200,
headers: {
'x-custom-header': 'my custom header value',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
message: resultQuery
? HTTP_STATUS_SUCCESS_MESSAGE
: HTTP_STATUS_FAILED_MESSAGE,
success: resultQuery,
}),
}
}
export const deleteOneItem = async event => {
const resultQuery = await deleteItem(event.pathParameters.itemId)
return {
statusCode: 200,
headers: {
'x-custom-header': 'my custom header value',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
message: resultQuery
? HTTP_STATUS_SUCCESS_MESSAGE
: HTTP_STATUS_FAILED_MESSAGE,
success: resultQuery,
}),
}
}

추가 될 부분은 메시지에 대한 부분인데 constants.js 로 분리하였다.

sample/src/common/js/constants.js

export const HTTP_STATUS_SUCCESS_MESSAGE = `it's done successfully`
export const HTTP_STATUS_FAILED_MESSAGE = `it occurs some errors during processing function inside.`

자 이제 람다 함수와 serverless.yml 까지 다 완성이 되었다. 작성된 람다 함수와 Serverless.yml 에 설정된 부분들을 serverless-offline 플러그인 서버로 확인해 보도록 하겠다.

배포 전 Serverless offline 으로 테스트 해보기

작성된 Full Code를 serverless-offline 플러그인에서 제공하는 로컬 서버로 AWS 에 Deploy 하기 전에 테스트 하는 과정을 진행해 보겠다.

먼저, 플러그인을 사전에 제대로 설치하고 코드를 제대로 입력했던 사용자라면 아래의 명령어가 제대로 먹을 것이다.

serverless offline --alias local // or 축약해서sls offline --alias local

위의 명령어를 치고 오타가 없다면 아래와 같이 정상적으로 서버가 가동된 것을 확인 할 수 있다.

http://localhost:3000 으로 접속 할 수 있다 path는 serverless.yml 에 함수 쪽에 정의된 path 로 접근 할 수 있다. 참고로 serverless-offline 의 부가적인 설정을 위해서는 github에 docs 를 확인하기 바란다.

Curl 테스트 해보기

사실 이 부분도 mock 테스트 라이브러리를 활용하여 코드로 테스트해 볼 수 있겠지만 그 부분은 생략하도록 하겠다. 일반적인 Postman 이나 curl 명령어로 테스트 가능하다. 어떤 것을 사용해도 무방하니 편한 본인만의 Tool로 테스트 해보자

POST /item

curl -X POST \\
<http://localhost:3000/item> \\
-d '{
"name" : "이정주",
"memo" : "아하하하...."
}'

결과

GET /items

curl -X GET <http://localhost:3000/items>

결과

이쁘게 나오지 않는 것을 감안하여 봐주길 바란다. -ㅇ-

GET /item/{itemId}

curl -X GET <http://localhost:3000/item/6>

결과

PUT /item

curl -X PUT \\
<http://localhost:3000/item> \\
-H 'Content-Type: application/json' \\
-d '{
"name" : "이정주",
"memo" : "변경됨....",
"id" : 6
}'

결과

test 테이블 조회 시 업데이트 된 데이터

DELETE /item/${itemId}

curl -X DELETE <http://localhost:3000/item/1>

결과

테스트가 완료되었다. Serverless Framework를 통해 AWS 에 배포하기전 람다 함수 및 설정한 API Path 가 제대로 작동 하는지 확인하였다. 다음 편에선 배포를 해보도록 하겠다!

다음 편 ▶︎ 6부. Serverless Framework 으로 배포 및 API 테스트

--

--