Abstract
Introducing the concept of BFF (Backend for Frontend)
through a Simple Application with SvelteKit
and Supabase
.
BFF (Backend for Frontend)
. Functional Server Block을 조합하여 Frontend 친화적인 Server Layer 를 구현, 이를 통해 개발의 효율성을 늘린다 라는 개념. 그러나 사실상 MicroService Architecture
가 기반이 되는 큰 단위의 서비스에 한해서나 의미가 있는 개념이다.
그렇다고해도 이러한 구조적인 개념을 소규모 프로젝트에 적용시키는 것이 불가능한 일은 아니다.
이번 포스팅에서는 Full Stack Application인 SvelteKit
과 BaaS인 Supabase
를 통해 유사한 개념을 미리 소규모로 테스트해보는 내용을 소개하고자한다.
이때, DB와 Functional Server Block의 역할을 오픈 소스 BaaS (Backend as a Service)
인 Supabase
로, Frontend 와 BFF 역할은 Svelte-kit
으로 구현하였다.
또한, 기존의 REST
방식과는 다른 GraphQL Codegen
을 이용한 서버와 클라이언트간의 소통 방법을 소개하고자 한다.
이 포스팅을 읽기 전에 하기 포스팅을 읽고 오는 것을 권장한다.
Getting Started
Setting up Supabase
Supabase 가입 후 Project 생성 뒤 Dashboard 진입
본 포스팅에서는 DB 스키마 및 테이블을 따로 작성하지 않고 Quick Start
예제를 들고와서 작업
SQL Editor > Countries > Run
을 통해 DB 생성 후 Table Editor 선택해서 데이터 확인
개별 데이터 조회 위해 function
생성 (Supabase
feature)
SQL Editor > New Query
에서 다음 명령어 실행 후 확인
SQL Editor > New Query
/* Create Function */ create or replace function get_country() returns setof countries language sql as $$ select * from countries; $$; /* Check Result */ select * from get_country() where id = 1;
Setting up SvelteKit
원하는 프로젝트 폴더를 생성한 뒤 SvelteKit
프로젝트를 생성
pnpm create svelte@latest ✔ Where should we create your project? (leave blank to use current directory) … simple-bff ✔ Which Svelte app template? › Skeleton project ✔ Add type checking with TypeScript? › Yes, using TypeScript syntax ✔ Add ESLint for code linting? … No / Yes ✔ Add Prettier for code formatting? … No / Yes ? Add Playwright for browser testing? › No / Yes
Setting up Supabase Client on SvelteKit
Supabase
클라이언트를 설치
Terminal
pnpm add -S @supabase/supabase-js
src/lib/db/index.ts
에 다음과 같이 Supabase 클라이언트 정의
src/lib/db/index.ts
import { createClient } from '@supabase/supabase-js'; import { ANON_KEY, PROJECT } from '$env/static/private'; // load from env const supabase = createClient(`https://${PROJECT}.supabase.co`, ANON_KEY); export default supabase;
Note
클라이언트 정보는 Supabase Dashboard 에서
Settings > API > URL
,Project API Keys
확인
src/lib/country/db/index.ts
, src/lib/countries/db/index.ts
에 해당하는 DB 요청 정의
요청한 DB 데이터가 잘 불러오지는지 확인
src/lib/country/db/index.ts
import supabase from '$lib/db'; // request id = 18 country data const country = await supabase.rpc('get_country').eq('id', 18); export default country;
src/lib/countries/db/index.ts
import supabase from '$lib/db'; // request all the countries const countries = await supabase.from('countries').select(); export default countries;
Setting up Graphql Yoga Server on SvelteKit Server
SvelteKit Server
구성. 여기서는 Graphql Yoga
를 이용해서 Graphql Server
를 구현
src/routes/graphql/+server.ts
에 서버 파일 작성
server api url 은 /graphql
로 접속 가능하다
Note
이 프로젝트는 Domain Base Structure 이기 때문에 각각 해당하는 graphql query, schema 가 산재되어 있음
graphql file loader 는 프로젝트 내 퍼져있는 graphql 파일을 한꺼 번에 모아줌
여기서
country.data[0]
,countries.data
는Supabase
의 요청 리턴값기타 나머지 부분에 대한 내용은 링크 참조:
https://the-guild.dev/graphql/yoga-server/docs/integrations/integration-with-sveltekit
Terminal
pnpm add -S graphql graphql-yoga pnpm add -D @graphql-tools/graphql-file-loader @graphql-tools/load
src/routes/graphql/+server.ts
import { createYoga, createSchema } from 'graphql-yoga'; import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; import { loadSchema, loadDocuments } from '@graphql-tools/load'; import country from '$lib/country/db'; import countries from '$lib/countries/db'; import type { RequestEvent } from '@sveltejs/kit'; const typeDefs = await loadSchema('./src/lib/**/graphql/schema.graphql', { loaders: [new GraphQLFileLoader()] }); const defaultQuery = await loadDocuments('./src/lib/countries/graphql/query.graphql', { loaders: [new GraphQLFileLoader()] }).then((res) => res[0].rawSDL); const yogaApp = createYoga<RequestEvent>({ schema: createSchema({ typeDefs, resolvers: { Query: { countries: () => countries.data, country: () => country.data[0] } } }), graphiql: { defaultQuery }, fetchAPI: globalThis }); export { yogaApp as GET, yogaApp as POST };
각각 해당하는 graphql schema와 query 파일 생성 (country, countries)
src/lib/country/graphql/schema.graphql
type Country { id: Int! name: String! iso2: String! iso3: String! local_name: String continent: String } type Query { country: Country }
src/lib/country/graphql/query.graphql
query Country { country { id name iso2 iso3 local_name continent } }
src/lib/countries/graphql/schema.graphql
type Query { countries: [Country]! }
src/lib/countries/graphql/query.graphql
query Countries { countries { id name iso2 iso3 local_name continent } }
이제 실행시켜서 /graphql
에 접속하여 playground 가 정상적으로 나타나는지 확인
Auto Generating Svelte Query with Graphql Codegen
codegen 관련 패키지 설치
Terminal
pnpm add -S graphql-request pnpm add -D @graphql-codegen/cli @graphql-codegen/near-operation-file-preset @graphql-codegen/typescript @graphql-codegen/typescript-graphql-request @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-query pnpm add -D @sveltestack/svelte-query
Note
@graphql-codegen/typescript-react-query 설치 근거
- graphql codegen 에서는 svelte query 는 지원 하지 않음
- 다만, tanstack에서 제공하는 react query, svelte query 는 naming convention 만 다를뿐 구조가 동일하기에 이 부분에 대한 수정만 해주면 사용 가능함
root 폴더에 codegen.yml 작성
package.json 에 codegen script 도 작성
Note
Graphql codegen 관련한 내용에 자세한 내용은 해당글 참조:
https://dev.to/soom/how-to-use-graphql-and-react-query-with-graphql-code-generator-based-on-nextjs-23jj
near-operation-file-preset
은 산재되어 있는 gql 파일을 확인 후 해당하는 폴더에 파일을 자동 생성기타 옵션에 대한 부분은 https://the-guild.dev/graphql/codegen/plugins 참조
아래 작성한 내용대로 진행되면 각각 graphql 폴더안에
query.generated.ts
생성되며 해당하는 type은src/lib/types/index.ts
파일이 생성
codegen.yml
schema: http://localhost:5173/graphql documents: './src/lib/**/graphql/!(*.generated).{gql,graphql}' require: - ts-node/register generates: src/lib/types/index.ts: plugins: - typescript src/: preset: near-operation-file presetConfig: extension: .generated.ts baseTypesPath: 'lib/types' plugins: - typescript-operations - typescript-react-query config: pureMagicComment: true exposeQueryKeys: true exposeFetcher: true withHooks: true fetcher: graphql-request config: interfacePrefix: 'I' typesPrefix: 'I' skipTypename: true declarationKind: 'interface' noNamespaces: true hooks: afterOneFileWrite: 'prettier --plugin-search-dir . --write .'
package.json
{ ... "scripts": { ... "codegen": "graphql-codegen --config codegen.yml" }, ... }
svelte query 기본 환경 설정
src/lib/plugin/svelteQuery.ts
import { QueryClient } from '@sveltestack/svelte-query'; // Configure for static fetching from Server export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false } } });
src/routes/+layout.svelte
<script lang="ts"> import '../app.css'; import { queryClient } from '$lib/plugin/svelteQuery'; import { QueryClientProvider } from '@sveltestack/svelte-query'; </script> <QueryClientProvider client={queryClient}> <main class="flex justify-center items-center w-full h-[100vh]"> <slot /> </main> </QueryClientProvider>
codegen 실행 후 결과 확인
Terminal
# first, run svelte-kit app with server pnpm dev # auto generating! pnpm codegen > graphql-codegen --config codegen.yml ✔ Parse Configuration ✔ Generate outputs
src/lib/country/graphql
, src/lib/countries/graphql
폴더에 query.generated.ts
생성
src/lib/types/index.ts
파일 생성
여기서 타입은 정상적으로 생성되나 문제는 query.generated.ts
위에서 언급한대로 react-query 기반으로 생성했기에 svelte-query 에 맞게 수정이 필요하다.
아래 예시처럼 react-query
import package를 svelte query
에 맞게 정리
src/lib/country/graphql/query.generated.ts
// auto-generated react-query // import * as Types from '../../types'; // import { GraphQLClient } from 'graphql-request'; // import { RequestInit } from 'graphql-request/dist/types.dom'; // import { useQuery, UseQueryOptions } from '@tanstack/react-query'; // Convert for svelte-query import type * as Types from '$lib/types'; import { GraphQLClient } from 'graphql-request'; import { useQuery } from '@sveltestack/svelte-query'; import type { RequestInit } from 'graphql-request/dist/types.dom'; import type { UseQueryOptions } from '@sveltestack/svelte-query';
Setting up Frontend
이제 view
레벨 작성
SPA
는 마운트되면서 빈 index.html
에 js 패키지를 로딩하는 식으로 렌더링을 하기 때문에 SEO 에 굉장히 불리하나 SSR
은 Hydration technique
을 통해 필요한 데이터를 미리 로딩하여 SEO 에 장점을 가져갈수 있다
Note
Hydration
에 대한 자세한 내용 및Resumability
에 추가적인 내용은 하기 링크 참조
https://www.builder.io/blog/hydration-is-pure-overhead#resumability-a-no-overhead-alternative-to-hydrationTechnique 에 대한 자세한 설명은 하기 링크 참조
https://dev.to/soom/how-to-use-graphql-and-react-query-with-graphql-code-generator-based-on-nextjs-23jj
src/routes/country/+page.ts
import { error } from '@sveltejs/kit'; import { dehydrate } from '@sveltestack/svelte-query'; import { queryClient } from '$lib/plugin/svelteQuery'; import { useCountryQuery } from '$lib/country/graphql/query.generated'; import { GraphQLClient } from 'graphql-request'; import type { PageLoad } from './$types'; export const load = (async ({ route }) => { const gqlClient = new GraphQLClient('http://localhost:5173/graphql'); await queryClient.prefetchQuery(useCountryQuery.getKey(), useCountryQuery.fetcher(gqlClient)); if (route.id === '/country') { return { title: 'country', dehydratedState: dehydrate(queryClient) }; } throw error(404, 'Not found'); }) satisfies PageLoad;
src/routes/country/+page.svelte
<script lang="ts"> import { GraphQLClient } from 'graphql-request'; import { useCountryQuery } from '$lib/country/graphql/query.generated'; import type { PageData } from './$types'; export let data: PageData; const gqlClient = new GraphQLClient('http://localhost:5173/graphql'); const countriesQueryResult = useCountryQuery(gqlClient); const { country } = $countriesQueryResult.data!; </script> <svelte:head> <title>{data.title}</title> </svelte:head> <div class="text-center"> <h1 class="text-3xl font-bold">Hello Country!</h1> <a href="/"> > Back Home</a> <div class="my-2"> {country?.iso2} {country?.name} </div> </div>
Result
👉 CodeSandBox Sample Link
Conclusion
본 포스팅에서는 BFF
간단한 개념 모델을 Supabase
, SvelteKit
으로 구현해보았다.
큰 규모의 서비스에서는 Client, Server(BFF), Server, DB 등을 다 따로 나눠서 구성하겠지만 소규모의 간단한 예제를 통해서도 개념 자체를 이해하는데는 큰 무리가 없다.
또한, GraphQL Code Generator
를 이용해 Server 와 Client 간의 의사소통을 자동 생성으로 접근하는 방법을 소개하였다.
기존의 Swagger
와 같은 방식으로는 여전히 Client 와 Server 의 의사소통에는 한계가 있었다. 그러나 GQL Codegen
을 통해 자동 생성되는 Schema
를 이용하는 방식은 Server 쪽에 따로 문의할 필요없이 Human Error 를 최소화할 수 있기에 적극 권장하는 방법이다.
p.s
대부분의 GraphQL 의 서버나 클라이언트는 Apollo Server
, Apollo Client
등을 이용하지만 여기서는 GraphQL Yoga
, Svelte Query
를 이용하는 방법을 소개하였다.
GraphQL Yoga
가 v3 로 넘어오면서 Apollo Server
의 불편함을 크게 해결한 솔루션을 제공하고 있는데다 이를 지원하는 프로젝트인 GraphQL Guild
가 워낙 GraphQL
에 진심이기 때문에 앞으로도 큰 발전이 기대 가능하다.
The Guild URL: https://the-guild.dev/
Top comments (0)