5. 정리
로그인
//Entity User
export class User {
id!: number;
username!: string;
password!: string;
}
// Mutation login
const user = await em.findOne(User, { username: options.username });
req.session.userId = user.id;
로그인 화면에서 넘긴 username과 패스워드로 로그인에 성공하면 세션을 설정한다.
레디스는 key-value 방식으로 userId의 value값에 username으로 검색해 찾은 id값을 넣어 저장한다.
codegen
- GraphQL Code Generator
- GraphQL Code Generator is a CLI tool that can generate TypeScript typings out of a GraphQL schema.
yarn add -D @graphql-codegen/cli
yarn graphql-codegen init
? What type of application are you building? Application built with React
? Where is your schema?: (path or url) http://localhost:4000/graphql
? Where are your operations and fragments?: src/graphql/**/*.graphql
? Pick plugins: TypeScript (required by other typescript plugins), TypeScript Operations (operations and fragments), TypeScript React Apollo (typed components and HOCs)
? Where to write the output: src/generated/graphql.tsx
? Do you want to generate an introspection file? No
? How to name the config file? codegen.yml
? What script in package.json should run the codegen? gen
Graphql client로 urql을 사용하기 위해 codegen.yml에 “typescript-urql”을 추가한다.
src/graphql/** 경로에 파일을 넣고 *yarn gen* 실행 시 src/generaged 폴더에 graphql.tsx가 생성되고 여기서 필요한 쿼리를 import해 사용한다.
src/graphql/fragments/RegularUser.graphql
fragment RegularUser on User {
id
username
}
src/graphql/mutations/login.graphql
mutation Login($options: UsernamePasswordInput!) {
login(options: $options) {
errors {
field
message
}
user {
...RegularUser
}
}
}
src/graphql/queries/me.graphql
query Me {
me {
...RegularUser
}
}
# async me(@Ctx() { req, em }: MyContext) {}
# 로그인 시 return user
_app.tsx
import { LoginMutation, MeDocument, MeQuery } from "../generated/graphql";
urql
브라우저에서 넘겨준 username과 패스워드로 쿼리를 실행해야 한다.
- urql
- urql is a GraphQL client that exposes a set of helpers for several frameworks. It’s built to be highly customisable and versatile so you can take it from getting started with your first GraphQL project all the way to building complex apps and experimenting with GraphQL clients.
yarn add urql graphql
createClient로 Graphql 클라이언트를 생성할 수 있다.
//_app.tsx
import { createClient, Provider } from "urql";
const client = createClient({ url: "http://localhost:4000/graphql" });
function MyApp({ Component, pageProps }: any) {
return (
<Provider value={client}>
<ThemeProvider theme={theme}>
<CSSReset />
<Component {...pageProps} />
</ThemeProvider>
</Provider>
);
}
export default MyApp;
credentials
const client = createClient({
url: "http://localhost:4000/graphql",
fetchOptions: {
credentials: "include",
},
exchanges: [],
});
- fetchOptions
- Allows us to customize the options that will be passed to fetch when a request is sent to the given API url.
보통 fetch는 쿠키를 보내거나 받지 않는다.
사이트에서 사용자 세션을 유지 관리해야하는 경우 인증되지 않는 요청이 발생한다.
쿠키를 전송하기 위해서는 자격증명(credentials) 옵션을 반드시 설정해야 한다.
Graphcache
@urql/exchange-graphcache package exports the cacheExchange which replaces the default cacheExchange in @urql/core.
yarn add @urql/exchange-graphcache
const client = createClient({
url: "http://localhost:3000/graphql",
exchanges: [dedupExchange, cacheExchange({}), fetchExchange],
});
cacheExchange
(method) Cache.updateQuery(input: QueryInput, updater: (data: Data | null) => Data | null): void
updateQuery() can be used to update the data of a given query using an updater function
// @urql/exchange-graphcache
export interface QueryInput {
query: string | DocumentNode;
variables?: Variables;
}
cache.updateQuery 메소드는 첫번째로 { query, variables } 객체를, 두번째로 updater callback를 받는다.
updater 함수는 query data를 받아 해당 데이터의 업데이트된 버전을 return 한다.
import { cacheExchange, Cache, QueryInput } from "@urql/exchange-graphcache";
// generic
function betterUpdateQuery<Result, Query>(
cache: Cache,
qi: QueryInput,
result: any,
fn: (r: Result, q: Query) => Query
) {
// queryinput
return cache.updateQuery(qi, (data) => fn(result, data as any) as any);
}
로그인 시 세션을 바로 교체하기 위해 cache.updateQuery에 MeDocument(queries/me.graphqld의 code-gen 결과물)를 넣는다.
cacheExchange({
updates: {
Mutation: {
login: (_result, args, cache, info) => {
betterUpdateQuery<LoginMutation, MeQuery>(
cache, /*Cache*/
{ query: MeDocument }, /*QueryInput*/
_result, /*any*/
(result, query) => {
if (result.login.errors) {
return query;
} else {
return {
me: result.login.user,
};
}
}
/*fn: (r: Result, q: Query) => Query*/
);
},
//logout:()=>{}
//register:()=>{}
},
},
}),
pages/login.tsx
<Formik
initialValues=
onSubmit={async (values, { setErrors }) => {
const response = await login({ options: values });
if (response.data?.login.errors) {
[{ field: "username", message: "something wrong" }];
setErrors(toErrorMap(response.data.login.errors));
} else if (response.data?.login.user) {
router.push("/");
}
}}
>
<Wrapper>
<Form>
<InputField />
<InputField />
<Button>login</Button>
</Form>
</Wrapper>
</Formik>
로그아웃
@Mutation(() => Boolean)
logout(@Ctx() { req, res }: MyContext) {
/*Since the method is async,
the reflection metadata system
shows the return type as a Promise*/
return new Promise((resolve) =>
//req.session.destroy() 레디스 서버에 저장된 세션 삭제
req.session.destroy((err) => {
res.clearCookie(COOKIE_NAME);
if (err) {
console.log(err);
resolve(false);
return;
}
resolve(true);
})
);
}