노트 생성 API 추가하기
노트 생성 API 추가하기
노트 앱을 위한 API를 만들어 보겠습니다.
먼저 노트를 생성하는 API를 추가할 것입니다. 이 API는 노트 객체를 입력으로 받아서 새로운 ID와 함께 데이터베이스에 저장합니다. 노트 객체는 content
필드(노트의 내용)와 attachment
필드(업로드된 파일의 URL)를 포함합니다.
API 생성하기
infra/api.ts
파일을 다음 코드로 교체하세요.
import { table } from "./storage";
// API 생성
export const api = new sst.aws.ApiGatewayV2("Api", {
transform: {
route: {
handler: {
link: [table],
},
}
}
});
api.route("POST /notes", "packages/functions/src/create.main");
여기서 몇 가지 중요한 작업을 수행하고 있습니다.
-
SST의
Api
컴포넌트를 사용해 API를 생성합니다. 이는 Amazon API Gateway HTTP API를 만듭니다. -
link
속성을 사용해 DynamoDB 테이블을 API에 연결합니다. 이를 통해 API가 테이블에 접근할 수 있게 됩니다. -
API에 추가하는 첫 번째 라우트는
POST /notes
입니다. 이 라우트는 노트를 생성하는 데 사용됩니다. -
transform
속성을 사용해 API의 모든 라우트에 주어진 속성을 적용하도록 지정합니다.
함수 추가하기
이제 노트를 생성할 함수를 추가해 보겠습니다.
packages/functions/src/create.ts
파일을 생성하고 다음 내용을 추가합니다.
import * as uuid from "uuid";
import { Resource } from "sst";
import { APIGatewayProxyEvent } from "aws-lambda";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const dynamoDb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export async function main(event: APIGatewayProxyEvent) {
let data, params;
// 요청 본문은 'event.body'에 JSON 문자열로 전달됨
if (event.body) {
data = JSON.parse(event.body);
params = {
TableName: Resource.Notes.name,
Item: {
// 생성할 아이템의 속성
userId: "123", // 작성자 ID
noteId: uuid.v1(), // 고유한 uuid
content: data.content, // 요청 본문에서 파싱
attachment: data.attachment, // 요청 본문에서 파싱
createdAt: Date.now(), // 현재 유닉스 타임스탬프
},
};
} else {
return {
statusCode: 404,
body: JSON.stringify({ error: true }),
};
}
try {
await dynamoDb.send(new PutCommand(params));
return {
statusCode: 200,
body: JSON.stringify(params.Item),
};
} catch (error) {
let message;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
return {
statusCode: 500,
body: JSON.stringify({ error: message }),
};
}
}
코드에 유용한 주석이 있지만 간단히 살펴보겠습니다.
event.body
에서 입력을 파싱합니다. 이는 HTTP 요청 본문을 나타냅니다.- 노트의 내용을 문자열로 포함하는
content
가 있습니다. attachment
도 포함될 수 있습니다. 이는 S3 버킷에 업로드될 파일의 이름입니다.- SST SDK를 사용해
Resource.Notes.name
을 통해 연결된 DynamoDB 테이블에 접근할 수 있습니다. 여기서Notes
는 SST에서 DynamoDB 테이블 생성 챕터에서 정의한 테이블 컴포넌트의 이름입니다. 이전에link: [table]
을 설정함으로써 API가 테이블에 접근할 수 있도록 했습니다. userId
는 노트 작성자의 ID입니다. 현재는123
으로 하드코딩되어 있습니다. 나중에 인증된 사용자에 따라 이 값을 설정할 것입니다.- DynamoDB에 새로운 객체를 생성하기 위해
noteId
와 현재 날짜를createdAt
으로 설정해 호출합니다. - DynamoDB 호출이 실패하면 HTTP 상태 코드
500
과 함께 오류를 반환합니다.
이제 여기서 사용할 패키지를 설치해 보겠습니다.
터미널에서 functions
폴더로 이동합니다.
$ cd packages/functions
그리고 packages/functions/
폴더에서 다음 명령어를 실행합니다.
$ npm install uuid @aws-sdk/lib-dynamodb @aws-sdk/client-dynamodb
$ npm install -D @types/uuid @types/aws-lambda
- uuid는 고유 ID를 생성합니다.
- @types/aws-lambda와 @types/uuid는 TypeScript 타입을 제공합니다.
- @aws-sdk/lib-dynamodb와 @aws-sdk/client-dynamodb는 DynamoDB와 통신할 수 있게 해줍니다.
변경 사항 배포하기
터미널로 이동하면 변경 사항이 배포되고 있는 것을 확인할 수 있습니다.
배포가 완료되면 다음과 같은 메시지를 볼 수 있습니다.
+ Complete
Api: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
API 테스트
이제 새 API를 테스트할 준비가 되었습니다.
터미널에서 다음 명령어를 실행하세요.
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"content":"Hello World","attachment":"hello.jpg"}' \
<YOUR_Api>/notes
<YOUR_Api>
를 위 출력 결과의 Api
로 바꿔주세요. 예를 들어, 명령어는 다음과 같이 보일 것입니다.
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"content":"Hello World","attachment":"hello.jpg"}' \
https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes
여기서는 노트 생성 API에 POST 요청을 보내고 있습니다. content
와 attachment
를 JSON 문자열로 전달합니다. 이 경우 attachment
는 가상의 파일 이름입니다. 아직 S3에 아무것도 업로드하지 않았습니다.
응답은 다음과 같이 보일 것입니다.
{"userId":"123","noteId":"a46b7fe0-008d-11ec-a6d5-a1d39a077784","content":"Hello World","attachment":"hello.jpg","createdAt":1629336889054}
noteId
를 기록해두세요. 다음 장에서 이 새로 생성된 노트를 사용할 것입니다.
코드 리팩토링
다음 장으로 넘어가기 전에 코드를 리팩토링해 보겠습니다. 모든 API에서 동일한 기본 작업을 수행할 것이므로, 이를 core
패키지로 옮기는 것이 합리적입니다.
먼저 create.ts
를 다음 코드로 교체하세요.
import * as uuid from "uuid";
import { Resource } from "sst";
import { Util } from "@notes/core/util";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const dynamoDb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const main = Util.handler(async (event) => {
let data = {
content: "",
attachment: "",
};
if (event.body != null) {
data = JSON.parse(event.body);
}
const params = {
TableName: Resource.Notes.name,
Item: {
// 생성할 아이템의 속성
userId: "123", // 작성자 ID
noteId: uuid.v1(), // 고유한 UUID
content: data.content, // 요청 본문에서 파싱
attachment: data.attachment, // 요청 본문에서 파싱
createdAt: Date.now(), // 현재 Unix 타임스탬프
},
};
await dynamoDb.send(new PutCommand(params));
return JSON.stringify(params.Item);
});
이 코드는 아직 작동하지 않지만, 우리가 달성하고자 하는 목표를 보여줍니다:
- Lambda 함수를
async
로 만들고 결과를 간단히 반환하고 싶습니다. - Lambda 함수에서 발생하는 모든 오류를 중앙에서 처리하고 싶습니다.
- 마지막으로, 모든 Lambda 함수가 API 엔드포인트를 처리할 것이므로 HTTP 응답을 한 곳에서 처리하고 싶습니다.
packages/core/src/util/index.ts
파일을 생성하고 다음 코드를 추가하세요.
import { Context, APIGatewayProxyEvent } from "aws-lambda";
export module Util {
export function handler(
lambda: (evt: APIGatewayProxyEvent, context: Context) => Promise<string>
) {
return async function(event: APIGatewayProxyEvent, context: Context) {
let body: string, statusCode: number;
try {
// Lambda 실행
body = await lambda(event, context);
statusCode = 200;
} catch (error) {
statusCode = 500;
body = JSON.stringify({
error: error instanceof Error ? error.message : String(error),
});
}
// HTTP 응답 반환
return {
body,
statusCode,
};
};
}
}
이제 core
에서도 Lambda 타입을 사용합니다. packages/core/
디렉토리에서 다음 명령어를 실행하세요.
$ npm install -D @types/aws-lambda
이제 이 코드를 자세히 살펴보겠습니다.
- Lambda 함수를 감싸는
handler
함수를 생성합니다. - 이 함수는 Lambda 함수를 인자로 받습니다.
- 그런 다음
try/catch
블록 안에서 Lambda 함수를 실행합니다. - 성공하면 결과를 받아
200
상태 코드와 함께 반환합니다. - 오류가 발생하면 오류 메시지를
500
상태 코드와 함께 반환합니다. - 모든 것을
Util
모듈 내부에 내보내면Util.handler
로 가져올 수 있습니다. 또한 나중에 이 모듈에 다른 유틸리티 함수를 추가할 수 있습니다.
템플릿 파일 제거
현재 사용 중인 템플릿에는 예제 파일이 포함되어 있습니다. 이제 이 파일들을 제거할 수 있습니다.
프로젝트 루트에서 다음 명령어를 실행하세요.
$ rm -rf packages/core/src/example packages/functions/src/api.ts
다음으로, 특정 ID를 가진 노트를 가져오는 API를 추가할 예정입니다.
일반적인 문제
-
path가 undefined 타입을 받음
npx sst dev
를 다시 시작하면 새로운 타입 정보를 인식하고 이 오류를 해결할 수 있습니다. -
응답
statusCode: 500
함수를 호출할 때
statusCode: 500
응답이 표시되면,catch
블록에서 우리 코드가 오류를 보고한 것입니다. 위의util/index.ts
코드에console.error
가 포함되어 있습니다. 이러한 로그를 추가하면 문제를 파악하고 해결하는 데 도움이 됩니다.} catch (e) { // 전체 오류를 출력 console.error(e); body = { error: e.message }; statusCode = 500; }
For help and discussion
Comments on this chapter