supabase는 PostqreSQL을 기반으로 SQL 쿼리 및 다양한 데이터베이스 작업을 수행할 수 있는 오픈소스 데이터베이스이다. supabase는 인증, 보안 등 백엔드 기능을 제공하기 때문에 백엔드 서버를 구축하지 않아도 클라이언트 앱을 빠르게 개발할 수 있다는 장점이 있다. 이를 활용해 로그인과 회원가입 기능을 구현해보았다.
인증(로그인, 회원가입) API 파일을 별도로 구성하여 회원가입을 수행하는 함수를 만들었다.
// @/api/auth.api.ts
async function signUp(signUpData: AuthData) {
const res = await supabase.auth.signUp(signUpData);
if (!!res.error) {
let message = "";
switch (res.error.status) {
case 422:
message = "이미 있는 로그인 정보입니다";
break;
case 400:
message = "로그인 정보가 올바르지 않습니다";
break;
}
const error = new CustomError(res.error.status as number, message);
throw error;
}
return res.data;
}
만약 supabase의 응답으로 에러가 왔을 경우, 에러객체에 따라 메시지와 상태코드를 지정하고, 에러를 throw한다.
그렇지 않다면 supabase의 응답을 반환한다.
회원가입 폼에서는 Tanstack-Query의 useMutation 훅을 활용해서 회원가입 작업 수행 및 성공했을 때 혹은 에러가 발생했을 때의 작업을 핸들링 하였다.
// app/(providers)/(root)/(auth)/sign-up/_components/signUpForm.tsx
const { mutate: signUp } = useMutation({
mutationFn: (signUpData: SignUpData) => AuthAPI.signUp(signUpData),
onSuccess() {
router.push("/log-in");
},
onError: (data) => {
throwErrorMsg("global", data.message);
},
});
작업 수행에 성공하였을 경우 router로 로그인 페이지로 이동시켜 사용자가 로그인을 수행할 수 있도록 하였다.
에러가 발생했을 경우, errorMessage State에 메시지를 삽입하고 화면에 표시한다.
form의 onSubmit 이벤트를 핸들링하여 input에 작성된 값들을 validation하고 회원가입에 필요한 객체를 구성한다
// app/(providers)/(root)/(auth)/sign-up/_components/signUpForm.tsx
const handleSubmitSignUp: ComponentProps<"form">["onSubmit"] = async (
e
) => {
e.preventDefault();
... // validation
const signUpData = {
email,
password,
};
const response = signUp(signUpData);
};
useMutation을 활용해 추출한 함수를 활용해 회원가입 작업을 수행한다.
로그인 작업 또한 유사한 형태로 진행한다. 회원가입과 달리 supabase에서는 다양한 로그인 방법을 제공하는데, 나는 email과 password를 기반으로 로그인하는 함수인 signInWithPassword 메서드를 사용하였다.
// @/api/auth.api.ts
async function logIn(logInData: AuthData) {
const res = await supabase.auth.signInWithPassword(logInData);
if (!!res.error) {
let message = "";
switch (res.error.status) {
case 400:
message = "로그인 정보가 올바르지 않습니다";
break;
}
const error = new CustomError(res.error.status as number, message);
throw error;
}
return res.data;
}
useMutation을 활용해 success, error 핸들링을 하도록 작성하였다.
// @/(providers)/(root)/(auth)/log-in/_components/LogInForm.tsx
const { mutate: logIn } = useMutation({
mutationFn: (logInData: AuthData) => AuthAPI.logIn(logInData),
onSuccess: () => {
router.replace("/");
},
onError: () => {
return throwErrorMsg("global", "로그인 정보가 잘못되었습니다");
},
});
로그인을 했다면 로그인이 된 상태가 유지되어야 사용자 경험이 올라간다. 만약 로그인했는데 새로고침을 했다는 이유로 다시 로그인해야 되는 상황만 상상해도 불편하기 때문이다. 따라서 contextAPI를 활용해서 전역변수로 로그인의 여부를 확인할 수 있도록 작성했다.
// @/contexts/auth.context.tsx
...
export function AuthContextProvider({ children }: PropsWithChildren) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [isAuthInitialized, setIsAuthInitialized] = useState(false);
const initializeAuth = () => {
setIsAuthInitialized(true);
};
const value = {
isLoggedIn,
isAuthInitialized,
setIsLoggedIn,
initializeAuth,
};
return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
}
AuthProvider 라는 Provider를 별도로 작성하여 사용자가 페이지를 로딩했을 때마다 로그인의 여부를 확인하고 전역변수를 변경해주는 작업을 해주었다.
// @/app/(providers)/_providers/auth.provider.tsx
function AuthProvider({ children }: PropsWithChildren) {
const { initializeAuth, setIsLoggedIn } = useAuth();
useEffect(() => {
supabase.auth.onAuthStateChange((_, session) => {
if (session?.user) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
initializeAuth();
});
}, []);
return children;
}
function ProviderLayout({ children }: PropsWithChildren) {
return (
<TanstackProvider>
<AuthContextProvider>
<AuthProvider>{children}</AuthProvider>
</AuthContextProvider>
</TanstackProvider>
);
}
'Today I Learned > Task' 카테고리의 다른 글
[Task] 이미지 입력 받아 백엔드에 저장하기 (0) | 2024.09.27 |
---|---|
[Task] Next.js에서 백엔드 구현 (1) | 2024.09.27 |
[Task] Express와 Firestore 연동하기 (3) | 2024.09.20 |
[Task] Express & React 연동하기 (0) | 2024.09.20 |
[Task] 최소화 상태의 검색바에서 가상선택자 스타일 없애기 (0) | 2024.09.03 |