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에 아..
질문:스키마 새로 작성시 마이그레이션, 클라이언트 생성 은 맨날 반복하면 되남유?답변:네 맞습니다! 스키마를 변경할 때마다 반복해야 해요.스키마 변경 시 워크플로우# 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 -..
질문: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..
RSC에서 QueryClient 패턴질문: prisma client 혹은 다른 여러 이런 종류 클라이언트들은 singleton 으로 쓰는 경우 많은데,왜 tanstack-query server side 에서는 매번 new QueryClient() 하나유?왜 매번 새로 생성하나요?// 각 요청마다 독립적인 QueryClient가 필요async function PostPage() { const queryClient = new QueryClient() // 서버 요청별로 새 인스턴스 await queryClient.prefetchQuery({...}) return 이유서버사이드는 stateless: 각 요청이 독립적요청간 격리: 다른 사용자의 데이터가 섞이면 안됨메모리 누수 방지: 요청 완료 후 GC로 ..
post-commit(husky)post-commit 파일은 husky로 pre-commit 하여 linting 을 수행했을 때, linting 되면서 변경되는 파일들이 많아서 해당 파일들도 한번더 커밋 하도록 한 것인데, 파일별로 커밋을 나눠서 하고 싶을 때에도 모든 파일을 커밋해버린다거나 하는 문제가 있어요. 해결할 방법이 있을까요?#!/usr/bin/env sh. "$(dirname -- "$0")/_/husky.sh"# Get the list of staged files before lintingSTAGED_FILES=$(git diff --cached --name-only)# Run lintingpnpm lintpnpm format# Get the list of modified files aft..
Hasura란?Hasura는 GraphQL 기반의 실시간 API 생성 엔진으로, 데이터베이스에서 자동으로 GraphQL API를 생성해주는 도구입니다. 특히 PostgreSQL과 잘 연동되며, 최소한의 설정만으로 강력한 CRUD API 및 실시간 데이터 쿼리 기능을 제공할 수 있습니다.🔹 주요 특징자동 GraphQL API 생성PostgreSQL, MySQL, SQL Server 등의 데이터베이스에서 자동으로 GraphQL API를 생성함.REST API를 따로 개발할 필요 없이 빠르게 백엔드 구축 가능.실시간 데이터 처리 (Subscriptions)GraphQL의 subscriptions 기능을 기본적으로 지원하여, 데이터 변경 시 자동으로 프론트엔드에 반영됨.WebSocket을 활용하여 실시간 데이..
1. husky 추가pnpm add husky2. husky initpnpm exec husky init3. package.json 수정lint 와 format 만 수정{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint --cache .", "prepare": "husky", "format": "prettier --cache --write ." }}4. pre-commit, pre-push 파일 작성pre-commitpnpm lint pre-pushpnpm format 추가. build 및 chore 커밋 자동화위까지 설정해도 린팅 및 포맷팅이 잘 되..
Q1props 로 특정한 정수 값을 받고 그 값이 변경될 때 화면에 숫자가 올라가거나 내려가면서 표시되는 애니메이션을 구현하고 싶어요. A1AnimatedNumberFramerMotion 컴포넌트를 수정해서, props로 전달된 value가 변경될 때 화면에 숫자가 올라가거나 내려가면서 표시되도록 만들 수 있습니다.방법:motionValue와 useEffect를 사용하여 value의 변화를 감지.useSpring으로 부드러운 애니메이션 적용.motion.span에 숫자가 연속적으로 보이도록 useTransform 사용. 코드:'use client';import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion';import {..
1. Apollo Client 설정 (App Router 버전)Apollo Client 설정 (lib/apolloClient.ts)클라이언트 환경에서 Apollo Client를 생성하는 설정입니다.// lib/apolloClient.tsimport { ApolloClient, InMemoryCache } from "@apollo/client";import { createUploadLink } from "apollo-upload-client";const apolloClient = new ApolloClient({ link: createUploadLink({ uri: process.env.NEXT_PUBLIC_API_BASE_URL, credentials: "include", // 쿠키 포함 ..
cn : tailwind-merge 와 clsx를 결합한 유틸리티 함수import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));} 이런 유틸리티 함수를 찾았는데아주 유용해 보였습니다. 그런데 이전 프로젝트에서 twMerge 함수만으로도clsx 없이 유사하게 썼던 기억이 있어서차이점에 대해 다시 짚어봤습니다. 결론은위처럼 cn (이름이야뭐,,,) 함수를 쓰는 것이clsx 의 여러 기능도 사용하면서 tailwind 유틸리티 클래스들을효과적으로 병합할 수 있는 좋은 방법이다였습니다..
next font 와 tailwind 로 모노레포에 폰트 적용하기모노레포 구성은 다음과 같은 상황입니다. /apps/admin => nextjs app/apps/invitation => nextjs app/apps/www => nextjs app/apps/storybook => storybook app/packages/config-tailwind => 테일윈드 공용 설정들 모여 있는 곳/packages/shared => 공용 컴포넌트 디렉토리(nextjs 아님) 이때 같은 폰트들을 각 app 들이 모두 사용합니다.처음에 생각한 것은 shared 에만 폰트를 설정하고각 app 에서 임포트 해서 쓰는 방법이었는데shared는 nextjs가 아니어서 next font를 사용할 수 없었습니다. 또한 storyb..
zod는 TypeScript와 JavaScript에서 사용할 수 있는 스키마 선언 및 검증 라이브러리입니다. zod를 사용하면 객체, 배열, 숫자, 문자열 등 다양한 데이터 구조를 스키마로 정의하고, 이를 기반으로 데이터의 유효성을 검증할 수 있습니다. 이를 통해 타입 안정성과 런타임에서의 데이터 검증을 동시에 수행할 수 있습니다.왜 zod를 사용하는가?타입 안전성: zod는 TypeScript와 자연스럽게 통합되어 스키마에서 정의한 데이터 구조에 대해 TypeScript 타입을 자동으로 추론합니다.런타임 검증: TypeScript는 컴파일 타임에서만 타입을 검사합니다. zod는 런타임에서도 데이터가 올바른지 확인할 수 있어, API 응답이나 폼 입력 데이터를 검증할 때 유용합니다.간편한 사용법: zod..
openai api 로 이미지 생성 후 supabase 에 쓰기(route handler)대략 아래와 같이 하면 됩니다.근데 한번 생성할 때 마다 돈나가고 10달러 충천해놔야 합니다.if (!tripImageFile && mode === 'new' && tripData) { const openai = new OpenAI({ apiKey: OPEN_AI_SECRET_KEY, }); // [서울/경기, 서울시] 이런 값입니다 대충 const [continent, country] = tripData.trip_final_destination?.split(' ') || []; // OpenAI를 사용하여 이미지를 생성 const imageGeneration = a..
tailwind-merge 라이브러리import { clsx, type ClassValue } from 'clsx';import { twMerge } from 'tailwind-merge';export function tailwindMerge(...inputs: ClassValue[]) { // clsx를 사용하여 입력된 모든 클래스네임을 결합하고, // 이를 twMerge에 전달하여 최종 병합된 클래스네임을 반환합니다. return twMerge(clsx(...inputs));} 간단히 위처럼 사용할 수 있습니다.GPT 쌤의 자세한 설명을 첨부합니다.clsx와 twMerge를 함께 사용하여 tailwindMerge 함수를 구현하면, 이 함수는 clsx만을 사용하는 경우보다 더 강력하고 ..
1. 라이브러리 설치리액트 인터섹션 옵저버 설치npm install react-intersection-observer탠스택쿼리 설치npm install @tanstack/react-query @tanstack/react-query-devtools2. 탠스택쿼리 프로바이더 설정프로바이더 만들기"use client";import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query";import { ReactQueryDevtools } from "@tanstack/react-query-devtools";function makeQueryClient() { return new QueryClient({ default..
cva with 타입스크립트?포켓몬 도감 비스무리한 개인과제 프로젝트를 하면서,cva 라이브러리를 사용 중에 타입지정에 매우 애를 먹었습니다.원래 이렇게 쓰는건가 싶습니다...? 더 공부해봐야겠습니다 ㅠㅠ아래의 예는 포켓몬의 타입 18개에만 해당하는 것이라 문제가 덜하지만속성인지 뭔지는 게임의 세대가 변화될때마다 추가되어서 뭐 몇개인지도 모르겠어요. 이것을 다 리터럴 타입으로 지정하자니 너무 힘들고 해서 mapped type 을 사용하긴 했는데되는건지 뭔지 모르겠습니다? 관련 내용을 정리합니다.cva chip 컴포넌트 만들기포켓몬의 18가지 타입에 맞는 Chip 컴포넌트를 만들기 위해 기존의 chipVariants 객체에 포켓몬 타입별 색상을 추가합니다.각각의 타입에 적절한 배경색, 테두리색, 텍스트 색..
이번엔 tanstack query 의 공식문서 중 Advanced Server Rendering 항목을 공부하였습니다.아래 내용을 정리합니다. 이번 포스팅 내용이 app router 에 해당합니다.Advanced Server Rendering이 가이드에서는 스트리밍, 서버 컴포넌트 및 Next.js 앱 라우터와 함께 React 쿼리를 사용하는 모든 것을 배울 수 있습니다. 이 가이드에 앞서 서버 렌더링 및 하이드레이션 가이드를 읽어보시는 것이 좋으며, 이 가이드에서는 SSR과 함께 React Query를 사용하기 위한 기본 사항을 설명하고 성능 및 요청 워터폴과 프리페칭 및 라우터 통합에 대해서도 유용한 배경 지식을 담고 있습니다. 시작하기 전에 SSR 가이드에 설명된 초기데이터 접근 방식은 서버 컴포넌..
이번엔 tanstack query 의 공식문서 중 Server Rendering & Hydration 항목을 공부하였습니다.아래 내용을 정리합니다. 중요! 이번 포스팅 내용은 페이지 라우터 버전입니다. 앱 라우터 버전은 다음 포스팅에 있습니다 Server Rendering & Hydration이 가이드에서는 서버 렌더링에 React 쿼리를 사용하는 방법을 알아봅니다.배경 지식은 프리페칭 및 라우터 통합 가이드를 참조하세요. 그 전에 성능 및 request waterfall 가이드도 확인해 보세요.스트리밍, 서버 컴포넌트 및 새로운 Next.js 앱 라우터와 같은 고급 서버 렌더링 패턴에 대해서는 고급 서버 렌더링 가이드를 참조하세요.서버 렌더링 & React 쿼리그렇다면 서버 렌더링이란 무엇일까요?이 가..