해당 글은 [AWS] 서버리스 스택-2와 이어집니다.
이번 글에서는 메모 API 수정, 삭제, 조회 등에 대해서 작성을 하였습니다.
코드 리팩터링
작성할 대부분의 API에 DynamoDB 요청과 비슷한 동작을 구현할 것이므로 공통적인 사항을 모듈화 해보자.
우선 src/util 경로로 폴더를 생성해주고 src/util/dynamodb.ts 파일을 생성하고 아래처럼 코드를 만들어주자.
// src/util/dynamodb.ts
import AWS from 'aws-sdk';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
const client: DocumentClient = new AWS.DynamoDB.DocumentClient();
export default {
get: (params: DocumentClient.GetItemInput) => client.get(params).promise(),
put: (params: DocumentClient.PutItemInput) => client.put(params).promise(),
query: (params: DocumentClient.QueryInput) => client.query(params).promise(),
update: (params: DocumentClient.UpdateItemInput) => client.update(params).promise(),
delete: (params: DocumentClient.DeleteItemInput) => client.delete(params).promise(),
};
DynamoDB의 관련된 기능을 모두 사용할 것이기 때문에 관련 메서드를 미리 구현해놓고 사용하기 쉽게 구성한다.
src/util/handler.ts 경로에 파일을 생성하고 아래처럼 코드를 만들어주자.
// src/util/handler.ts
import { APIGatewayProxyResult, Callback, Context, Handler } from 'aws-lambda';
export default function handler(lambda: Handler) {
return async function (
event: any,
context: Context,
callback: Callback
): Promise<APIGatewayProxyResult> {
let body, statusCode;
try {
// 람다 실행
body = await lambda(event, context, callback);
statusCode = 200;
} catch (e: any) {
console.error(e);
body = { error: e.message };
statusCode = 500;
}
// HTTP 응답 return
return {
statusCode,
body: JSON.stringify(body),
};
};
}
handler 함수를 공통으로 사용하기 위해 아래처럼 기능하도록 작성했다.
- handler 함수는 Lambda 함수를 감싸는 용도의 함수 역할
- Lambda 함수를 인수로 받아옴
- try/catch 문으로 Lambda 함수를 실행
- 성공 시 JSON.stringify를 통해 결과와 200 상태 코드를 반환
- 오류가 있으면 500 상태 코드와 함께 오류 메시지를 반환
src/create.ts 경로에 있는 코드를 아래처럼 수정해주자.
// src/create.ts
import * as uuid from 'uuid';
import handler from './util/handler';
import dynamoDb from './util/dynamodb';
export const main = handler(async (event) => {
// JSON으로 데이터를 넘겨 'event.body' 에서 파싱이 필요하다
const data = JSON.parse(event.body);
// DynamoDB에서 필요한 인자들
const params = {
TableName: process.env.TABLE_NAME!,
Item: {
userId: '123', // 사용자 ID
noteId: uuid.v1(), // 메모를 생성할 때 생성되는 고유ID (uuid 라이브러리 사용)
content: data.content, // event.body 쪽에서 받은 정보
attachment: data.attachment, // event.body 쪽에서 받은 정보
createdAt: Date.now(), // 생성된 시간
},
};
await dynamoDb.put(params);
return params.Item;
});
코드를 수정함으로써 얻는 효과는 아래와 같다.
- Lambda 함수를 async 함수로 처리하고 단순히 결과를 반환하도록 한다.
- DynamoDB의 인스턴스를 거의 모든 API에서 사용할 것이므로 공통적으로 처리하는 모듈을 사용한다.
- 우리는 Lambda 함수의 모든 오류를 공통적으로 처리하도록 한다.
- 마지막으로 모든 Lambda 함수가 API 엔드포인트를 처리하므로 한 곳에서 HTTP 응답을 처리한다.
메모 조회 기능 추가
이전에 메모 생성 API를 테스트하면서 userId=123인 데이터를 저장했으므로 이제 해당 ID가 지정된 메모를 검색하는 API를 추가해보자.
src/get.ts 경로에 파일을 추가하고 아래처럼 코드를 작성하자.
// src/get.ts
import handler from "./util/handler";
import dynamoDb from "./util/dynamodb";
export const main = handler(async (event) => {
const params = {
TableName: process.env.TABLE_NAME!,
// DyanomDB에서 primaryKey, sortKey 2개가 제공되어야 한다.
Key: {
userId: "123", // primaryKey (유저아이디)
noteId: event.pathParameters.id, // sortKey (메모아이디)
},
};
const result = await dynamoDb.get(params);
if (!result.Item) {
throw new Error("Item not found.");
}
// 조회한 결과를 돌려준다.
return result.Item;
});
stacks/ApiStack.ts 파일에 "GET /notes/{id}": "src/get.main" 메모를 조회하는 API 경로를 추가해주자.
// stacks/ApiStack.ts
import * as sst from "@serverless-stack/resources";
export default class ApiStack extends sst.Stack {
// 다른 스택에서 접근할 수 있도록 선언
api;
constructor(scope: sst.App, id: string, props?: any) {
super(scope, id, props);
const { table } = props;
// API 생성 (두번째 인자에 본인 이니셜을 포함해 ID를 넣어주자)
this.api = new sst.Api(this, "api-sykim", {
defaultFunctionProps: {
environment: {
TABLE_NAME: table.tableName,
},
},
routes: {
"POST /notes": "src/create.main", // 메모 생성 API
"GET /notes/{id}": "src/get.main", // 메모 조회 API
},
});
// API가 DynamoDB 테이블에 접근할 수 있도록 권한 설정
this.api.attachPermissions([table]);
// API의 EndPoint Url을 노출
this.addOutputs({
ApiEndpoint: this.api.url,
});
}
}
변경사항 배포
터미널에서 아래 명령어를 통해 AWS에 리소스를 배포하고 테스트를 진행하자
npx sst start
배포가 완료되면 터미널에서 아래와 비슷한 문구가 나와야 한다.
Stack dev-notes-api
Status: deployed
Outputs:
ApiEndpoint: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
배포 완료 후 직접 HTTP GET 요청을 만들어 ApiEndPoint에 나와있는 주소와 경로 /notes/"메모ID" 경로를 구성하여 테스트를 진행하면 아래처럼 응답이 나와야 한다.
(지난번에 기록해놓은 notesId와 함께 https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes/57708320-aa5b-11ec-8f0f-e37a7bfbc143 로 요청해야 함.)
반드시 본인이 생성한 NoteId 값을 포함해야 한다.
POSTMAN을 사용한다면 https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes/:id 처럼 요청 주소를 적어주면 끝에 ID를 입력할 수 있는 Path Variables를 구성할 수 있다.
메모 목록 조회 기능 추가
사용자가 가지고 있는 모든 메모를 조회하는 API를 추가해보자.
src/list.ts 경로에 파일을 추가하고 아래처럼 코드를 작성하자.
// src/list.ts
import handler from "./util/handler";
import dynamoDb from "./util/dynamodb";
export const main = handler(async (event) => {
const params = {
TableName: process.env.TABLE_NAME!,
// 'KeyConditionExpression' dynamoDB에서 query에 사용되는 키값을 정의
// - 'userId = :userId' 이것은 userId 키와 일치하는 값만 조회하게 된다
// partition key (userId와 일치하는 모든 목록을 조회한다)
KeyConditionExpression: "userId = :userId",
// 'ExpressionAttributeValues' 위에 KeyConditionExpresion에 매칭되는 값을 정의
// - ':userId': 'userId'의 값을 정의한다.
ExpressionAttributeValues: {
":userId": "123",
},
};
const result = await dynamoDb.query(params);
// 조회된 목록을 반환
return result.Items;
});
stacks/ApiStack.ts 파일에 "GET /notes": "src/list.main" 메모 목록을 조회하는 API 경로를 추가해주자.
// stacks/ApiStack.ts
import * as sst from '@serverless-stack/resources';
export default class ApiStack extends sst.Stack {
// 다른 스택에서 접근할 수 있도록 선언
api;
constructor(scope: sst.App, id: string, props?: any) {
super(scope, id, props);
const { table } = props;
// API 생성 (두번째 인자에 본인 이니셜을 포함해 ID를 넣어주자)
this.api = new sst.Api(this, 'api-sykim', {
defaultFunctionProps: {
environment: {
TABLE_NAME: table.tableName,
},
},
routes: {
'POST /notes': 'src/create.main', // 메모 생성 API
'GET /notes/{id}': 'src/get.main', // 메모 조회 API
'GET /notes': 'src/list.main', // 메모 목록 조회 API
},
});
// API가 DynamoDB 테이블에 접근할 수 있도록 권한 설정
this.api.attachPermissions([table]);
// API의 EndPoint Url을 노출
this.addOutputs({
ApiEndpoint: this.api.url,
});
}
}
변경사항 배포
터미널에서 아래 명령어를 통해 AWS에 리소스를 배포하고 테스트를 진행하자
npx sst start
배포가 완료되면 터미널에서 아래와 비슷한 문구가 나와야 한다.
Stack dev-notes-api
Status: deployed
Outputs:
ApiEndpoint: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
배포 완료 후 직접 HTTP GET 요청을 만들어 ApiEndPoint에 나와있는 주소와 경로 /notes 경로를 구성하여 테스트를 진행하면 아래처럼 응답이 나와야 한다.
메모 수정 기능 추가
사용자가 가지고 있는 메모를 수정하는 API를 추가해보자.
src/update.ts 경로에 파일을 추가하고 아래처럼 코드를 작성하자.
// src/update.ts
import handler from "./util/handler";
import dynamoDb from "./util/dynamodb";
export const main = handler(async (event) => {
const data = JSON.parse(event.body);
const params = {
TableName: process.env.TABLE_NAME!,
// 'Key' 수정하기 위해 수정해야 할 데이터의 Key를 정의
Key: {
userId: "123", // 사용자 ID
noteId: event.pathParameters.id, // 노트 ID
},
// 'UpdateExpression' 업데이트 할 필드 정의
// 'ExpressionAttributeValues' 업데이트할 값을 정의
UpdateExpression: "SET content = :content, attachment = :attachment",
ExpressionAttributeValues: {
":attachment": data.attachment || null, // 수정할 파일이름
":content": data.content || null, // 수정할 메모 내용
},
// 'ReturnValues' 이 설정에 따라 업데이트된 후 어떤 값을 돌려줄 지 설정가능
ReturnValues: "ALL_NEW",
};
await dynamoDb.update(params);
return { status: true };
});
stacks/ApiStack.ts 파일에 "PUT /notes/{id}": "src/update.main" 메모를 수정하는 API 경로를 추가해주자.
// stacks/ApiStack.ts
import * as sst from '@serverless-stack/resources';
export default class ApiStack extends sst.Stack {
// 다른 스택에서 접근할 수 있도록 선언
api;
constructor(scope: sst.App, id: string, props?: any) {
super(scope, id, props);
const { table } = props;
// API 생성 (두번째 인자에 본인 이니셜을 포함해 ID를 넣어주자)
this.api = new sst.Api(this, 'api-sykim', {
defaultFunctionProps: {
environment: {
TABLE_NAME: table.tableName,
},
},
routes: {
'POST /notes': 'src/create.main', // 메모 생성 API
'GET /notes/{id}': 'src/get.main', // 메모 조회 API
'GET /notes': 'src/list.main', // 메모 목록 조회 API
'PUT /notes/{id}': 'src/update.main', // 메모 수정 API
},
});
// API가 DynamoDB 테이블에 접근할 수 있도록 권한 설정
this.api.attachPermissions([table]);
// API의 EndPoint Url을 노출
this.addOutputs({
ApiEndpoint: this.api.url,
});
}
}
변경사항 배포
터미널에서 아래 명령어를 통해 AWS에 리소스를 배포하고 테스트를 진행하자
npx sst start
배포가 완료되면 터미널에서 아래와 비슷한 문구가 나와야 한다.
Stack dev-notes-api
Status: deployed
Outputs:
ApiEndpoint: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
배포 완료 후 직접 HTTP PUT요청을 만들어 ApiEndPoint에 나와있는 주소와 경로 /notes/"메모ID" 경로를 구성하여 테스트를 진행하면 아래처럼 응답이 나와야 한다.
(지난번에 기록해놓은 notesId와 함께 https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes/57708320-aa5b-11ec-8f0f-e37a7bfbc143 로 요청해야 함.)
반드시 본인이 생성한 NoteId 값을 포함해야 한다.
HTTP PUT 요청을 보낼 때 실제 클라이언트에서 보내는 것처럼 HTTP Body에 아래처럼 JSON 데이터를 만들어서 보내야 한다.
(이해가 되지 않는다면 반드시 이전에 src/update.ts 파일에 선언한 event.body를 어떻게 사용하는지 살펴봐야 한다.)
{"content":"New World","attachment":"new.jpg"}
메모 삭제 기능 추가
사용자가 가지고 있는 메모를 삭제하는 API를 추가해보자.
src/delete.ts 경로에 파일을 추가하고 아래처럼 코드를 작성하자.
// src/delete.ts
import handler from './util/handler';
import dynamoDb from './util/dynamodb';
export const main = handler(async (event) => {
const params = {
TableName: process.env.TABLE_NAME!,
// 'Key' 삭제할 데이터의 키값 정의
Key: {
userId: '123', // 사용자 ID
noteId: event.pathParameters.id, // 노트 ID
},
};
await dynamoDb.delete(params);
return { status: true };
});
stacks/ApiStack.ts 파일에 "DELETE /notes/{id}": "src/delete.main" 메모를 수정하는 API 경로를 추가해주자.
// stacks/ApiStack.ts
import * as sst from '@serverless-stack/resources';
export default class ApiStack extends sst.Stack {
// 다른 스택에서 접근할 수 있도록 선언
api;
constructor(scope: sst.App, id: string, props?: any) {
super(scope, id, props);
const { table } = props;
// API 생성 (두번째 인자에 본인 이니셜을 포함해 ID를 넣어주자)
this.api = new sst.Api(this, 'api-sykim', {
defaultFunctionProps: {
environment: {
TABLE_NAME: table.tableName,
},
},
routes: {
'POST /notes': 'src/create.main', // 메모 생성 API
'GET /notes/{id}': 'src/get.main', // 메모 조회 API
'GET /notes': 'src/list.main', // 메모 목록 조회 API
'PUT /notes/{id}': 'src/update.main', // 메모 수정 API
'DELETE /notes/{id}': 'src/delete.main', // 메모 삭제 API
},
});
// API가 DynamoDB 테이블에 접근할 수 있도록 권한 설정
this.api.attachPermissions([table]);
// API의 EndPoint Url을 노출
this.addOutputs({
ApiEndpoint: this.api.url,
});
}
}
변경사항 배포
터미널에서 아래 명령어를 통해 AWS에 리소스를 배포하고 테스트를 진행하자
npx sst start
배포가 완료되면 터미널에서 아래와 비슷한 문구가 나와야 한다.
Stack dev-notes-api
Status: deployed
Outputs:
ApiEndpoint: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
배포 완료 후 직접 HTTP DELETE 요청을 만들어 ApiEndPoint에 나와있는 주소와 경로 /notes/"메모ID" 경로를 구성하여 테스트를 진행하면 아래처럼 응답이 나와야 한다.
(지난번에 기록해놓은 notesId와 함께 https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes/57708320-aa5b-11ec-8f0f-e37a7bfbc143 로 요청해야 함.)
반드시 본인이 생성한 NoteId 값을 포함해야 한다.
POSTMAN을 사용한다면 https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes/:id 처럼 요청 주소를 적어주면 끝에 노트 ID를 입력할 수 있는 Path Variables를 구성할 수 있다.
메모 API 작성을 마무리하며 내용을 개인 Github에 올려주도록 하자.
서버리스 스택-3 정리
- DynamoDB, Lambda 함수 리팩토링
- 메모 조회 API 생성
- 메모 목록 조회 API 생성
- 메모 수정 API 생성
- 메모 삭제 API 생성
이번 문서에서는 위와 같이 메모 관련 API를 모두 작성하였습니다.
다음 내용은 사용자 인증 및 API 보안 등에 대해서 [AWS] 서버리스 스택-4 파트에서 정리하도록 하겠습니다.
'IT > Serverless Stack' 카테고리의 다른 글
[AWS] 서버리스 스택-4 (0) | 2022.04.06 |
---|---|
[AWS] 서버리스 스택-2 (0) | 2022.03.22 |
[AWS] 서버리스 스택-1 (0) | 2022.03.21 |