DEV Community

Cover image for Introducing the BFF (Backend for Frontend) Concept by simple application with SvelteKit, Supabase, and GraphQL Code Generator
soom
soom

Posted on • Edited on

Introducing the BFF (Backend for Frontend) Concept by simple application with SvelteKit, Supabase, and GraphQL Code Generator

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 을 이용한 서버와 클라이언트간의 소통 방법을 소개하고자 한다.

이 포스팅을 읽기 전에 하기 포스팅을 읽고 오는 것을 권장한다.

https://dev.to/soom/how-to-use-graphql-and-react-query-with-graphql-code-generator-based-on-nextjs-23jj


Getting Started

Setting up Supabase

Supabase 가입 후 Project 생성 뒤 Dashboard 진입

본 포스팅에서는 DB 스키마 및 테이블을 따로 작성하지 않고 Quick Start 예제를 들고와서 작업

SQL Editor > Countries > Run 을 통해 DB 생성 후 Table Editor 선택해서 데이터 확인

supabase1

supabase2

supabase3


개별 데이터 조회 위해 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; 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Setting up Supabase Client on SvelteKit

Supabase 클라이언트를 설치

Terminal
pnpm add -S @supabase/supabase-js 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

Note

클라이언트 정보는 Supabase Dashboard 에서 Settings > API > URL, Project API Keys 확인

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; 
Enter fullscreen mode Exit fullscreen mode
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; 
Enter fullscreen mode Exit fullscreen mode

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.dataSupabase 의 요청 리턴값

  • 기타 나머지 부분에 대한 내용은 링크 참조:
    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 
Enter fullscreen mode Exit fullscreen mode
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 }; 
Enter fullscreen mode Exit fullscreen mode

각각 해당하는 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 } 
Enter fullscreen mode Exit fullscreen mode
src/lib/country/graphql/query.graphql
query Country { country { id name iso2 iso3 local_name continent } } 
Enter fullscreen mode Exit fullscreen mode
src/lib/countries/graphql/schema.graphql
type Query { countries: [Country]! } 
Enter fullscreen mode Exit fullscreen mode
src/lib/countries/graphql/query.graphql
query Countries { countries { id name iso2 iso3 local_name continent } } 
Enter fullscreen mode Exit fullscreen mode

이제 실행시켜서 /graphql에 접속하여 playground 가 정상적으로 나타나는지 확인

query


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 
Enter fullscreen mode Exit fullscreen mode

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

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 .' 
Enter fullscreen mode Exit fullscreen mode
package.json
{ ... "scripts": { ... "codegen": "graphql-codegen --config codegen.yml" }, ... } 
Enter fullscreen mode Exit fullscreen mode

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 } } }); 
Enter fullscreen mode Exit fullscreen mode
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> 
Enter fullscreen mode Exit fullscreen mode

codegen 실행 후 결과 확인

Terminal
# first, run svelte-kit app with server pnpm dev # auto generating! pnpm codegen > graphql-codegen --config codegen.yml ✔ Parse Configuration ✔ Generate outputs 
Enter fullscreen mode Exit fullscreen mode

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'; 
Enter fullscreen mode Exit fullscreen mode

Setting up Frontend

이제 view 레벨 작성

SPA 는 마운트되면서 빈 index.html 에 js 패키지를 로딩하는 식으로 렌더링을 하기 때문에 SEO 에 굉장히 불리하나 SSRHydration technique을 통해 필요한 데이터를 미리 로딩하여 SEO 에 장점을 가져갈수 있다

Note

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; 
Enter fullscreen mode Exit fullscreen mode
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> 
Enter fullscreen mode Exit fullscreen mode

Result

👉 CodeSandBox Sample Link

Result


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)