AWS AppSync를 사용해 서버리스 GraphQL API 만들기
AWS AppSync로 서버리스 GraphQL API 만들기
이 예제에서는 SST를 사용해 AWS에서 AppSync GraphQL API를 만드는 방법을 살펴보겠습니다. 사용자가 노트를 조회, 생성, 수정, 삭제, 목록 조회할 수 있도록 할 것입니다.
SST의 Live Lambda Development를 사용할 예정입니다. 이를 통해 AppSync를 로컬에서 변경하고 테스트할 수 있으며, 재배포할 필요가 없습니다.
실제 동작 영상은 다음과 같습니다.
요구사항
- Node.js 16 이상
- TypeScript 사용
- AWS 계정과 로컬에 구성된 AWS CLI
SST 앱 생성하기
먼저 SST 앱을 만들어 보겠습니다.
$ npx create-sst@latest --template=base/example graphql-appsync
$ cd graphql-appsync
$ npm install
기본적으로 앱은 AWS us-east-1
리전에 배포됩니다. 이 설정은 프로젝트 루트의 sst.config.ts
파일에서 변경할 수 있습니다.
import { SSTConfig } from "sst";
export default {
config(_input) {
return {
name: "graphql-appsync",
region: "us-east-1",
};
},
} satisfies SSTConfig;
프로젝트 구조
SST 앱은 두 부분으로 구성됩니다.
-
stacks/
— 앱 인프라서버리스 앱의 인프라를 정의하는 코드는 프로젝트의
stacks/
디렉토리에 위치합니다. SST는 AWS CDK를 사용하여 인프라를 생성합니다. -
packages/functions/
— 앱 코드API가 호출될 때 실행되는 코드는 프로젝트의
packages/functions/
디렉토리에 위치합니다.
인프라 설정하기
먼저 AppSync API를 정의해 보겠습니다.
stacks/ExampleStack.ts
파일을 다음 코드로 교체하세요.
import { StackContext, Table, AppSyncApi } from "sst/constructs";
export function ExampleStack({ stack }: StackContext) {
// 노트 테이블 생성
const notesTable = new Table(stack, "Notes", {
fields: {
id: "string",
},
primaryIndex: { partitionKey: "id" },
});
// AppSync GraphQL API 생성
const api = new AppSyncApi(stack, "AppSyncApi", {
schema: "packages/functions/src/graphql/schema.graphql",
defaults: {
function: {
// 테이블 이름을 함수에 바인딩
bind: [notesTable],
},
},
dataSources: {
notes: "packages/functions/src/main.handler",
},
resolvers: {
"Query listNotes": "notes",
"Query getNoteById": "notes",
"Mutation createNote": "notes",
"Mutation updateNote": "notes",
"Mutation deleteNote": "notes",
},
});
// AppSync API ID와 API 키를 출력에 표시
stack.addOutputs({
ApiId: api.apiId,
APiUrl: api.url,
ApiKey: api.cdk.graphqlApi.apiKey || "",
});
}
여기서는 AppSyncApi
구성을 사용해 AppSync GraphQL API를 생성합니다. 또한 Table
구성을 사용해 DynamoDB 테이블을 만듭니다. 이 테이블은 GraphQL API로 생성할 노트를 저장합니다.
마지막으로, 테이블을 API에 바인딩합니다.
GraphQL 스키마 정의하기
packages/functions/src/graphql/schema.graphql
에 다음 내용을 추가합니다.
type Note {
id: ID!
content: String!
}
input NoteInput {
id: ID!
content: String!
}
input UpdateNoteInput {
id: ID!
content: String
}
type Query {
listNotes: [Note]
getNoteById(noteId: String!): Note
}
type Mutation {
createNote(note: NoteInput!): Note
deleteNote(noteId: String!): String
updateNote(note: UpdateNoteInput!): Note
}
노트 객체에 대한 타입도 추가합니다.
packages/functions/src/graphql/Note.ts
파일을 새로 생성하고 다음 내용을 추가합니다.
type Note = {
id: string;
content: string;
};
export default Note;
함수 핸들러 추가하기
먼저 AppSync 데이터 소스로 사용할 Lambda 함수를 만들어 보겠습니다.
packages/functions/src/main.ts
파일을 다음 코드로 교체하세요.
import Note from "./graphql/Note";
import listNotes from "./graphql/listNotes";
import createNote from "./graphql/createNote";
import updateNote from "./graphql/updateNote";
import deleteNote from "./graphql/deleteNote";
import getNoteById from "./graphql/getNoteById";
type AppSyncEvent = {
info: {
fieldName: string;
};
arguments: {
note: Note;
noteId: string;
};
};
export async function handler(
event: AppSyncEvent
): Promise<Record<string, unknown>[] | Note | string | null | undefined> {
switch (event.info.fieldName) {
case "listNotes":
return await listNotes();
case "createNote":
return await createNote(event.arguments.note);
case "updateNote":
return await updateNote(event.arguments.note);
case "deleteNote":
return await deleteNote(event.arguments.noteId);
case "getNoteById":
return await getNoteById(event.arguments.noteId);
default:
return null;
}
}
이제 리졸버를 구현해 보겠습니다.
노트 생성하기
노트를 생성하는 기능부터 시작해 보겠습니다.
packages/functions/src/graphql/createNote.ts
파일을 추가합니다.
import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";
import Note from "./Note";
const dynamoDb = new DynamoDB.DocumentClient();
export default async function createNote(note: Note): Promise<Note> {
const params = {
Item: note as Record<string, unknown>,
TableName: Table.Notes.tableName,
};
await dynamoDb.put(params).promise();
return note;
}
여기서는 주어진 노트를 DynamoDB 테이블에 저장합니다.
packages/functions/
폴더에서 사용 중인 aws-sdk
패키지를 설치합니다.
$ npm install aws-sdk
노트 목록 읽기
이제 모든 노트를 가져오는 함수를 작성해 보겠습니다.
packages/functions/src/graphql/listNotes.ts
에 다음 코드를 추가합니다.
import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";
const dynamoDb = new DynamoDB.DocumentClient();
export default async function listNotes(): Promise<
Record<string, unknown>[] | undefined
> {
const params = {
TableName: Table.Notes.tableName,
};
const data = await dynamoDb.scan(params).promise();
return data.Items;
}
여기서는 테이블에서 모든 노트를 가져옵니다.
특정 노트 읽기
단일 노트를 가져오는 함수도 비슷한 방식으로 구현할 것입니다.
packages/functions/src/graphql/getNoteById.ts
파일을 생성합니다.
import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";
import Note from "./Note";
const dynamoDb = new DynamoDB.DocumentClient();
export default async function getNoteById(
noteId: string
): Promise<Note | undefined> {
const params = {
Key: { id: noteId },
TableName: Table.Notes.tableName,
};
const { Item } = await dynamoDb.get(params).promise();
return Item as Note;
}
전달된 id를 가진 노트를 가져옵니다.
노트 업데이트하기
이제 노트를 업데이트해 보겠습니다.
packages/functions/src/graphql/updateNote.ts
파일을 추가하고 다음 코드를 작성합니다:
import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";
import Note from "./Note";
const dynamoDb = new DynamoDB.DocumentClient();
export default async function updateNote(note: Note): Promise<Note> {
const params = {
Key: { id: note.id },
ReturnValues: "UPDATED_NEW",
UpdateExpression: "SET content = :content",
TableName: Table.Notes.tableName,
ExpressionAttributeValues: { ":content": note.content },
};
await dynamoDb.update(params).promise();
return note as Note;
}
여기서는 전달된 노트의 id와 content를 사용해 노트를 업데이트합니다.
노트 삭제하기
모든 작업을 완료하기 위해 노트를 삭제해 보겠습니다.
packages/functions/src/graphql/deleteNote.ts
에 다음 코드를 추가하세요.
import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";
const dynamoDb = new DynamoDB.DocumentClient();
export default async function deleteNote(noteId: string): Promise<string> {
const params = {
Key: { id: noteId },
TableName: Table.Notes.tableName,
};
// await dynamoDb.delete(params).promise();
return noteId;
}
현재는 의도적으로 삭제 쿼리를 비활성화했습니다. 이 부분은 나중에 다시 다룰 예정입니다.
지금까지 만든 기능을 테스트해 보겠습니다!
개발 환경 시작하기
SST는 Live Lambda Development 환경을 제공합니다. 이를 통해 여러분은 서버리스 앱을 실시간으로 작업할 수 있습니다.
$ npm run dev
이 명령어를 처음 실행하면 앱과 Live Lambda Development 환경을 지원하는 디버그 스택을 배포하는 데 몇 분이 소요됩니다.
===============
앱 배포 중
===============
SST 앱 준비 중
소스 코드 변환 중
소스 코드 린팅 중
스택 배포 중
dev-graphql-appsync-ExampleStack: 배포 중...
✅ dev-graphql-appsync-ExampleStack
스택 dev-graphql-appsync-ExampleStack
상태: 배포됨
출력:
ApiId: lk2fgfxsizdstfb24c4y4dnad4
ApiKey: da2-3oknz5th4nbj5oobjz4jwid62q
ApiUrl: https://2ngraxbyo5cwdpsk47wgn3oafu.appsync-api.us-east-1.amazonaws.com/graphql
ApiId
는 방금 생성한 AppSync API의 ID이고, ApiKey
는 AppSync API의 API 키이며, ApiUrl
은 AppSync API의 URL입니다.
SST Console을 사용해 엔드포인트를 테스트해 보겠습니다. SST Console은 SST 앱을 관리할 수 있는 웹 기반 대시보드입니다. 문서에서 자세히 알아보세요.
GraphQL 탭으로 이동하면 GraphQL Playground가 실행 중인 것을 확인할 수 있습니다.
GraphQL 탐색기를 사용하면 앱에서 GraphQLApi와 AppSyncApi 구성을 통해 생성된 GraphQL 엔드포인트를 쿼리할 수 있습니다.
먼저 노트를 생성해 보겠습니다. 아래 뮤테이션을 플레이그라운드의 왼쪽 부분에 붙여넣으세요.
mutation createNote {
createNote(note: { id: "001", content: "My note" }) {
id
content
}
}
이제 SST Console의 DynamoDB 탭으로 이동해 테이블에 값이 생성되었는지 확인해 보겠습니다.
DynamoDB 탐색기를 사용하면 앱의 Table
구성에서 DynamoDB 테이블을 쿼리할 수 있습니다. 테이블을 스캔하거나 특정 키를 쿼리하고, 아이템을 생성하고 편집할 수 있습니다.
이제 방금 생성한 노트를 가져오기 위해 아래 쿼리를 실행해 보겠습니다.
query getNoteById {
getNoteById(noteId: "001") {
id
content
}
}
다음으로 업데이트 뮤테이션을 테스트해 보겠습니다.
mutation updateNote {
updateNote(note: { id: "001", content: "My updated note" }) {
id
content
}
}
이제 노트를 삭제해 보겠습니다.
mutation deleteNote {
deleteNote(noteId: "001")
}
삭제가 제대로 동작했는지 확인하기 위해 모든 노트를 가져오는 쿼리를 실행해 보겠습니다.
query listNotes {
listNotes {
id
content
}
}
몇 가지를 확인할 수 있습니다. 첫째, 생성한 노트가 여전히 존재합니다. 이는 deleteNote
메서드가 실제로 쿼리를 실행하지 않기 때문입니다. 둘째, 노트는 이전 쿼리에서 업데이트된 내용을 반영해야 합니다.
변경 사항 적용하기
packages/functions/src/graphql/deleteNote.ts
파일에서 쿼리의 주석을 제거해 수정해 보겠습니다.
await dynamoDb.delete(params).promise();
쿼리 편집기로 돌아가서 삭제 뮤테이션을 다시 실행해 보세요.
mutation deleteNote {
deleteNote(noteId: "001")
}
이제 목록 쿼리를 실행하면 노트가 제거된 것을 확인할 수 있습니다!
query listNotes {
listNotes {
id
content
}
}
앱을 다시 배포하지 않아도 변경 사항이 적용된 것을 확인할 수 있습니다.
API 배포하기
이제 API 테스트가 완료되었으니 프로덕션 환경에 배포해 보겠습니다. 여러분은 sst.config.ts
에 지정된 dev
환경을 사용하고 있었던 것을 기억할 겁니다. 하지만 이제는 다른 환경에 배포할 예정입니다. 이렇게 하면 다음에 로컬에서 개발할 때 사용자들이 사용하는 API가 망가지지 않습니다.
터미널에서 다음 명령어를 실행하세요.
$ npx sst deploy --stage prod
정리하기
마지막으로, 이 예제에서 생성된 리소스들을 다음 명령어로 제거할 수 있습니다.
$ npx sst remove
$ npx sst remove --stage prod
결론
여기까지입니다! 여러분은 AppSync로 구축한 새로운 서버리스 GraphQL API를 갖게 되었습니다. 테스트와 변경을 위한 로컬 개발 환경도 마련했고, 프로덕션에 배포까지 완료했습니다. 이제 사용자들과 공유할 수 있습니다. 아래 레포지토리에서 이 예제에서 사용한 코드를 확인해 보세요. 궁금한 점이 있다면 댓글을 남겨 주세요!
For help and discussion
Comments on this example