naver 로그인 구현 with Supabase 3편TL; DR: admin 권한 쓰면 끝작년 7월에 supabase에서 편법(?) naver 로그인 기능을 구현했었습니다.1년 넘게 지났는데 갑자기 편법 말고 진짜로 구현이 되겠잖아? 생각이 들었어요. supabase 어드민 권한을 쓰면 auth 스키마에도 입력이 될 것 같았고해봤더니 역시 아주 잘 되었습니다... 이 생각을 왜 작년엔 못했을까요? 한가지 문제는 네이버 OAuth 를 프로덕션으로 쓰려면네이버 개발자센터 > 내 애플리케이션 > 네이버 로그인 검수 상태에서 검수가 완료되어야 합니다. 검수가이드 보면 대단한 걸 요구하는건 아닌데 귀찮습니다.카카오는 검수 없이도 기본적인 정보들은 그냥 받을 수 있는데네이버는 아무튼간 안되는 것 같습니다. 결론은,..
분류 전체보기
🍪 Cookie, CORS, Site/Origin 총정리1. TL;DRexample.com(프론트엔드)에서 api.example.com(백엔드)으로 인증 요청을 보낼 때:Same-Site이지만 Cross-Origin입니다쿠키에 domain: ".example.com" 설정 필요sameSite: "lax" 충분 (Cross-Site 아니므로)백엔드 CORS 설정 필수 (별개 정책!)Apollo Client 등 라이브러리 credentials: "include" 필수핵심: 쿠키 정책(브라우저)과 CORS(서버) 둘 다 맞춰야 성공..2. Site vs Origin 개념📚 정의Origin = Protocol + Domain + Port (모두 일치해야 Same-Origin)Site = 루트 도메인(eTLD..
실전적 Apollo Client 구현기1. 문제 상황기술 스택Next.js 15 (App Router)Hasura GraphQLApollo Client with @apollo/client-integration-nextjs해결해야 했던 문제들중복 코드 문제: 서버용, 어드민용, 클라이언트용 총 3개의 Apollo Client 인스턴스가 필요했는데, 각각에 대해 Apollo Links를 별도로 작성하면 코드 중복이 심각함서버/클라이언트 경계 처리: RSC(React Server Components) 환경에서 서버와 클라이언트의 인증 방식이 달라 각각 다른 처리가 필요토큰 갱신 로직: 클라이언트에서만 토큰 갱신이 가능하므로 환경별로 다른 에러 처리 필요2. 해결 방안: 팩토리 패턴과 의존성 주입2.1 팩토리 ..
Hasura(GQL) 사용시 3rd-Party 쿠키 정책 문제Q) credentials: "include" 인데도 쿠키 자동 전송 안될 수 있는거 맞음?제가 cookie 기반에서 httponly, secured, domain, samesite 설명을 여러가지로 변주해서 시도해봤지만 결론은 브라우저의 third-party cookie 정책으로 인해 쿠키가 자동 전송되지 않는데 이게 맞나요?(심지어 서브도메인 - api.myapp.com myapp.com 이더라도)A) 맞다.맞아요. 결론부터 말하면: 브라우저가 Hasura로 쿠키를 보내주지 못하면(3rd‑party 쿠키 차단), Remote Schema를 써도 Hasura가 JWT를 검증할 수 없고, 따라서 x-hasura-user-id 같은 세션 변수도..
Server 용 Apollo Client 생성시 주의점!TL; DRregisterApolloClient는 싱글톤처럼 동작하지만, getToken을 함수로 전달하면 매 요청마다 토큰을 새로 읽어와서 안전할 수 있음. 참고하지만 캐시 오염 가능성은 여전히 존재하므로, 민감한 데이터는 fetchPolicy: 'no-cache' 사용을 권장.getToken 없이 credentials: 'include'만으로는 절대 작동하지 않음!가장 안전한 방법은 요청별 클라이언트를 생성하는 것이지만, getToken을 올바르게 구현하면 registerApolloClient만으로 충분히 사용 가능.코드 예시// /lib/apollo/server.tsimport { Defer20220824Handler } from "@apoll..
GraphQL Yoga 역할(+Hasura Remote Schema)예시// /api/graphql/route.tsimport { DateTimeTypeDefinition } from "graphql-scalars";import { createSchema, createYoga } from "graphql-yoga";import { v4 as uuid } from "uuid";interface NextContext { params: Promise>;}const typeDefs = /* GraphQL */ ` type Query { session: Session! } type Mutation { ok: String! } type Session { ..
Next.js 15 테스트 환경 구축 가이드0단계: 조합유닛테스트: Vitest통합테스트: React Testing Library + MSWE2E테스트: Playwright1단계: 테스트 환경 구축1️⃣ Vitest 설정 (유닛 테스트)pnpm add -D vitest @vitejs/plugin-react jsdompnpm add -D @testing-library/react @testing-library/jest-dom @testing-library/user-eventvitest.config.tsimport { defineConfig } from 'vitest/config'import react from '@vitejs/plugin-react'import path from 'path'export def..
0. 아래와 같은 Auth.js 기본 세팅 + apollo 세팅은 되어있다는 가정// lib/auth.tsimport NextAuth from "next-auth";import Google from "next-auth/providers/google";import { HasuraAdapter } from "@auth/hasura-adapter";import { JWT } from "next-auth/jwt";declare module "next-auth/jwt" { interface JWT { id: string; role: string; } }export const { handlers, signIn, signOut, auth } = NextAuth({ adap..
React Query에서 클래스 메서드를 queryFn으로 사용할 때 주의사항문제 상황React Query를 사용하다가 다음과 같은 에러를 만났습니다:Cannot read properties of undefined (reading 'get')코드는 다음과 같았습니다:// queries.tsexport const useUserQuery = () => { return useQuery({ queryKey: ['user'], queryFn: api.getUser, // 이 부분이 문제! });};// apis.tsclass ApiClient extends BaseApiClient { public getUser() { return this.get("/api/auth/user"); }}기본적..
Hasura 트랜잭션 처리: Actions와 Functions 활용하기📌 TL;DR (한 줄 요약)Hasura v2는 단일 mutation 내부는 자동 트랜잭션이지만,복잡한 로직은 Actions 또는 PostgreSQL Functions로 처리해야 합니다.🎯 핵심 정리단일 GraphQL mutation은 자동으로 트랜잭션 처리여러 mutation을 별도 호출하면 트랜잭션 아님Hasura Actions로 커스텀 비즈니스 로직 구현 (추천)PostgreSQL Functions (RPC)로 DB 레벨 트랜잭션 처리Apollo Client의 배치는 서버 트랜잭션이 아님1. Hasura의 트랜잭션 동작 방식✅ 자동 트랜잭션 (보장됨)# 하나의 mutation 요청 = 하나의 트랜잭션mutation Create..
PostgREST와 Hasura의 관계Hasura는 PostgREST를 사용하지 않습니다.Hasura와 PostgREST는 완전히 별개의 프로젝트!차이점 비교아키텍처Supabase:PostgreSQL → PostgREST → REST API → Supabase JS ClientHasura:PostgreSQL → Hasura Engine → GraphQL API → Apollo Client기술 스택 SupabaseHasura프로토콜REST (PostgREST 사용)GraphQL (자체 엔진)쿼리 언어URL + 체이닝GraphQL엔진PostgREST (Haskell)Hasura Engine (Haskell)철학REST + GraphQL 스타일 쿼리순수 GraphQL참고 블로그 글의 의미PostgREST? "..
Supabase의 PostgREST: GraphQL처럼 쿼리하는 SQL📌 TL;DR (한 줄 요약)Supabase는 PostgREST를 통해 GraphQL의 선언적 쿼리 스타일을 REST API로 구현하여, SQL JOIN을 직관적인 체이닝 메서드로 사용할 수 있게 합니다.🎯 핵심 정리Supabase JS는 SQL을 직접 쓰지 않고 체이닝 메서드 사용PostgREST는 GraphQL의 장점과 REST의 단순함을 결합한 프로토콜Foreign Key 기반으로 자동 관계 생성 - 별도 설정 불필요중첩 쿼리로 복잡한 JOIN도 간단하게 표현TypeScript 타입 자동 생성으로 완벽한 타입 안정성1. SQL JOIN의 전통적 방식전통적인 SQL JOIN-- 게시글 + 작성자 정보SELECT posts.i..
Supabase RPC: 트랜잭션과 보안을 위한 필수 도구📌 TL;DR (한 줄 요약)Supabase RPC는 성능 최적화뿐만 아니라 Row Level Security 하에서 안전한 트랜잭션 처리를 위한 핵심 기능.🎯 핵심 정리Supabase JS 클라이언트는 트랜잭션을 지원하지 않음RPC (PostgreSQL Functions)를 사용하면 서버 측에서 트랜잭션 보장RLS (Row Level Security)는 행 단위 접근 제어로 클라이언트의 직접 DB 접근을 안전하게 보호RLS 정책의 USING 절은 관계 테이블을 통한 복잡한 권한 체크 가능트랜잭션 + RLS를 함께 사용하면 안전하고 일관된 데이터 처리 가능1. 문제 상황: Supabase JS의 한계Supabase JS는 트랜잭션을 지원하지 않..
Terraform으로 Neon DB + Hasura GraphQL API 서버 구축하기프로젝트 개요백엔드 인프라: Neon PostgreSQL + Hasura (EC2) + AWS Secret Manager + IAM프론트엔드: Next.js (Vercel 배포 예정) + Apollo Client + GraphQL Code Generator패키지 매니저: pnpm1. 인프라 구축 (Terraform)1.1 사전 준비Neon DBneon.tech 가입 및 프로젝트 생성Connection string 복사 postgresql://[user]:[password]@[endpoint]/[dbname]?sslmode=requireSSH 키 준비# SSH 키가 없다면 생성ssh-keygen -t rsa -b 4096..
TanStack Query DevTools vs Apollo DevTools 비교TanStack Query DevTools ✨import { ReactQueryDevtools } from '@tanstack/react-query-devtools' // 기능:// ✅ 모든 쿼리 상태 실시간 확인// ✅ 캐시 데이터 탐색// ✅ 쿼리 무효화/리페치 버튼// ✅ 타임라인// ✅ 직관적인 UIApollo DevTools 😢// 브라우저 확장 프로그램 설치 필요// Chrome/Firefox Extension// 기능:// ⚠️ 쿼리 목록 (기본적)// ⚠️ 캐시 탐색 (복잡함)// ⚠️ Mutation 추적 (불편함)// ⚠️ UI가 투박함// ❌ 타임라인 없음// ❌ 실시간 업데이트 약함해결책: Cus..
문제 상황일반적인 Next.js + Apollo 설정// lib/apollo-client-rsc.ts (Server Component용)import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc'export const { getClient } = registerApolloClient(() => { return new ApolloClient({ cache: new InMemoryCache(), link: from([ loggerLink, // 중복 1 err..
ApolloLink = GraphQL 요청/응답 미들웨어Express.js의 미들웨어와 완전히 같은 개념이에요:Express.js 미들웨어// Expressapp.use((req, res, next) => { console.log('Request:', req.url) // 로깅 next() // 다음 미들웨어로})app.use((req, res, next) => { req.user = getCurrentUser() // 인증 next()})app.use((req, res, next) => { if (!req.user) { return res.status(401).send('Unauthorized') // 권한 체크 } next()})Apollo Link (같은 패턴!)// Apo..
캐시 키 비교TanStack Query// 명시적으로 캐시 키 지정useQuery({ queryKey: ['campaigns', { status: 'open', page: 1 }], queryFn: () => fetchCampaigns({ status: 'open', page: 1 })})// 캐시 구조{ '["campaigns",{"status":"open","page":1}]': { data: [...], ... }, '["campaigns",{"status":"open","page":2}]': { data: [...], ... },}Apollo Client// 캐시 키 자동 생성!useQuery(GET_CAMPAIGNS, { variables: { status: 'open', page: 1..
1. InMemoryCache의 typePolicies기본 개념: Apollo Client의 캐싱 메커니즘Apollo Client는 GraphQL 응답을 메모리에 캐싱해서 같은 데이터를 다시 요청할 때 네트워크 요청 없이 즉시 반환.const cache = new InMemoryCache({ typePolicies: { Query: { fields: { campaign_application: { keyArgs: ['where', 'order_by'], merge(existing, incoming) { return incoming }, }, }, }, },})상세 분석typePoli..
Hasura의 특별한 점일반 GraphQL 서버 (Apollo Server 등):GraphQL → 직접 작성한 리졸버 → SQL 쿼리 (여기서 DataLoader 필요!)Hasura:GraphQL → Hasura 엔진 → 최적화된 SQL 자동 생성 (DataLoader 불필요! 이미 내장됨)Hasura가 자동으로 해주는 것들1. 자동 JOIN 최적화query GetCampaign { campaign(where: { id: { _eq: "abc-123" } }) { id title applications { id status influencer { id username platforms { ..
Hasura(v2) GraphQL + Next.js + Apollo ClientHasura(v2) 로 GraphQL API서버를 만들고, DB는 local PostgreSQL 을 docker 로 띄울 것입니다.대충 반려동물 주제로 유저, 포스트, 좋아요, 댓글, 동물정보 테이블을 만들고,seeding은 faker.js 이용하여 typescript 로 합니다. 쿼리와 뮤테이션 작성후 codegen까지 완료되면 Next.js 15 app router 에 맞게apollo cilent 설정을 하고 프론트 개발을 하면 됩니다.순서프로젝트 init의존성 설치여기 참고하여 curl 로 docker-compose 파일 받기docker 띄우기(시딩용 ports: - "5432:5432" 추가필요)localhost:808..
axios client 의 실전적 구성1. 문제 정의axios 클라이언트 구성을 매번 그때그때 하다보니 문제가 생길때가 많음그러다보니 구조화가 되어있지 않아 어떻게했었지 하고 또 찾아봄요청, 응답 interceptor 작성을 관성적으로 했더니 사용할때 제네릭을 두번씩 api.get 이런식으로 쓰고 있었음2개 이상의 axios 클라이언트를 만들때(ex 서버용, 클라이언트용 등) 계층화가 안되어있어 불편타입 안전성 부족과 에러 처리가 일관성이 없음환경별 설정 관리가 어려움2. 해결 방안클래스로 axios api 클라이언트를 구성한다api-client 추상 클래스 에서는 axios instance 를 생성하고 기본 메서드를 오버라이드한다기본메서드를 오버라이드 하는 이유는 사용시 제네릭 입력 두번 안하고 편하게..
Biome 로 테일윈드 클래스 저장시 자동정렬하기biome.jsonclevel을 errorfix를 safe"nursery": { "useSortedClasses": { "level": "error", "fix": "safe", "options": { "attributes": [ "classList" ], "functions": [ "clsx", "cva", "cn" ] } }}.vscode/settings.jsoneditor.codeActionsOnSave에 아..
Next.js static build + s3 조합에서 라우팅이 안된다?그렇습니다. 아래처럼 next.config 설정하고const nextConfig: NextConfig = { output: "export", // 정적 파일만 trailingSlash: true, images: { unoptimized: true },};빌드하여 s3 에 올리고 CF 붙이면메인 index.html 은 잘 나오는데 라우팅이 안됩니다.클로드쌤과 함께 해결했습니다.CloudFront FunctionsLambda@Edge보다 비용이 저렴한 CloudFront Functions를 사용해 보았습니다.function handler(event) { var request = event.request; var..
DNS와 hosts 파일 동작 원리1. 일반적인 DNS 조회 과정브라우저 → hosts 파일 확인 → DNS 서버 조회 → IP 주소 반환 → 웹사이트 접속2. hosts 파일 수정 후 과정브라우저 → hosts 파일에서 직접 IP 발견 → DNS 서버 건너뛰고 바로 해당 IP로 접속hosts 수정하여 사용하기 단계별 정리단계 1: 현재 상황 파악원래 도메인(예: example.com)이 CloudFront → S3를 가리킴하지만 Cafe24 서버(000.000.000.000)의 내용을 확인해야 함단계 2: hosts 파일 수정로컬 hosts 파일에 다음과 같은 내용 추가:000.000.000.000 example.com단계 3: 결과브라우저에서 example.com 입력 시 DNS를 거치지 않고 바로 ..
패스워드 저장 방법패스워드는 절대 평문으로 저장하면 안 되고, 해싱해서 저장해야 합니다.1. bcrypt 설치pnpm add bcryptpnpm add -D @types/bcrypt2. 회원가입 시 패스워드 해싱// app/api/auth/signup/route.tsimport bcrypt from 'bcrypt';import { prisma } from '@/lib/prisma';export async function POST(request: Request) { const { email, nickname, password } = await request.json(); // 패스워드 해싱 (saltRounds: 10~12 권장) const hashedPassword = await bcrypt.has..
질문:스키마 새로 작성시 마이그레이션, 클라이언트 생성 은 맨날 반복하면 되남유?답변:네 맞습니다! 스키마를 변경할 때마다 반복해야 해요.스키마 변경 시 워크플로우# 1. schema.prisma 수정# 2. 마이그레이션 생성 & 적용npx prisma migrate dev --name add-user-model# 3. Prisma Client 재생성 (보통 자동으로 됨)npx prisma generate중요한 점들migrate dev의 장점npx prisma migrate dev는 마이그레이션 파일 생성 + DB 적용 + 클라이언트 생성을 한 번에 처리해요--name 옵션으로 마이그레이션에 의미있는 이름을 부여하세요실제 사용 예시# 처음 User 모델 만들 때npx prisma migrate dev -..
SSO vs OAuthSSO(Single Sign-On)와 OAuth는 인증(Authentication)과 인가(Authorization) 관점에서 서로 다릅니다. 간단히 비교하면 다음과 같습니다:✅ 목적의 차이항목SSO (Single Sign-On)OAuth (Open Authorization)핵심 목적하나의 로그인으로 여러 서비스 사용제3자 앱이 사용자의 자원에 접근 허용주요 개념인증(Authentication) 중심인가(Authorization) 중심✅ 예시SSO:회사 포털에 한 번 로그인하면 이메일, 회계, 인트라넷 등 다양한 시스템에 추가 로그인 없이 접근 가능OAuth:어떤 앱이 “구글 계정으로 로그인”할 때, 구글에서 인증하고 → 해당 앱이 사용자 이메일, 프로필 등 제한된 정보에 접근하도록..
질문:seed.ts 를 작성했는데, 이거 자동 반영되나유??답변:아니요! 시드 스크립트는 앱 시작과는 별개로 수동 실행해야 해요.시드 실행 방법1. 직접 실행npx tsx seed.ts# 또는node --loader ts-node/esm seed.ts2. package.json에 스크립트 추가 (권장){ "scripts": { "dev": "next dev", "build": "next build", "seed": "tsx seed.ts" }}그러면 이렇게 실행:pnpm run seed3. Prisma 공식 방법package.json에 prisma 설정 추가:{ "prisma": { "seed": "tsx prisma/seed.ts" }}그러면 이렇게 실행:npx prisma..
질문:src/generated/prisma/index.d.ts 에 다음 코드가 있는 걸 확인 했는데, export type Post = $Result.DefaultSelectionsrc/generated/prisma 는 gitignore 처리 되어 있어요.그러면 이거.. git 에 올라가지 않으니 vercel 배포시 오류날 것 같은데,저 export type Post 부분을 prisma 에서 제공하는 인터페이스? 등을 활용해서 제가 작성해야 하나유? 답변:아니요! Vercel 배포 시 자동으로 해결돼요. 걱정하지 마세요!Vercel 배포 과정1. 빌드 시 자동 생성// package.json{ "scripts": { "build": "next build", "postinstall": "pri..
