Computer >> 컴퓨터 >  >> 프로그램 작성 >> Redis

서버리스 Redis로 SvelteKit 애플리케이션 구축

SvelteKit은 컴파일 시간에 앱을 빌드하여 더 작고 빠른 JavaScript를 생성하는 UI 프레임워크인 Svelte를 위한 곧 출시될 전체 스택 애플리케이션 프레임워크입니다. SvelteKit을 사용하면 끝점을 사용하여 서버 측 논리를 작성할 수 있지만 애플리케이션 데이터를 유지하는 방법은 사용자에게 달려 있습니다.

이 게시물에서는 SvelteKit 애플리케이션에서 Redis를 사용하여 데이터를 저장하는 방법을 보여줍니다. Redis를 사용하여 영화 API 응답을 캐시하고 The Movie Database(TMDB) API의 데이터를 사용하여 임의의 영화를 표시합니다.

데모를 따라 하려면 Redis 연결 문자열이 필요하거나 Redis를 로컬에서 실행해야 합니다. Redis 인스턴스가 아직 없다면 Upstash를 추천합니다. SvelteKit과 마찬가지로 서버리스 애플리케이션에 최적화되어 있으며 요청이 많지 않은 경우 무료입니다(작성 ​​당시 한도는 10k/day). Redis 인스턴스를 얻는 위치에 관계없이 지연 시간을 줄이려면 애플리케이션이 배포된 위치와 가까운 위치에 있어야 합니다.

전제조건

  • SvelteKit에 대한 기본 지식(예:페이지 대 엔드포인트, 특정 페이지에 대한 쿼리 및 매개변수 로드, 검색)
  • 데모를 실행하거나 배포하려는 경우 TMDB API 키 및 Redis 인스턴스(예:Upstash에서)

초보자 개요

스타터 리포지토리를 복제합니다. main 분기에 최종 결과가 있으므로 initial을 확인하세요. 이 게시물과 함께 할 분기. 변경 사항을 푸시하려면 먼저 리포지토리를 분기하십시오.

git clone https://github.com/geoffrich/movie-search-redis.git
cd movie-search-redis
git checkout initial

이것은 TMDB API를 사용하여 영화를 검색하고 세부 정보를 볼 수 있는 작은 SvelteKit 응용 프로그램입니다. TypeScript를 사용하여 API 응답과 더 쉽게 상호 작용할 수 있지만 Redis 또는 SvelteKit을 사용하기 위한 요구 사항은 아닙니다. 다음 경로가 이미 존재합니다(src/routes의 파일에 해당). ):

  • / 홈페이지 렌더링
  • /search 검색 결과 목록을 렌더링합니다. query가 필요합니다. 및 page 쿼리 매개변수로, 예를 들어 ?query=star wars&page=3 "스타워즈" 결과의 세 번째 페이지를 보여줍니다.
  • /search.json TMDB API를 쿼리하고 결과 목록을 반환하는 서버 끝점입니다. /search와 동일한 쿼리 매개변수를 사용합니다.
  • /movie/[id] 주어진 ID로 영화에 대한 세부 정보를 렌더링합니다. /movie/11
  • /movie/[id].json TMDB API에서 영화 세부 정보를 반환하는 서버 끝점입니다. TMDB 응답은 페이지에서 사용하는 것보다 더 많은 데이터를 반환하므로 데이터의 하위 집합을 반환하도록 응답을 조정합니다.

Netlify에서 실행 중인 데모를 볼 수 있습니다.

TMDB API 호출은 서버 끝점에서만 발생합니다. 이는 API 키가 클라이언트에 노출되지 않도록 하기 위한 것입니다.

데모를 로컬에서 실행하려면 .env를 생성하세요. 프로젝트 루트에 파일을 추가하고 TMDB API 키와 Redis 연결 문자열을 추가합니다(아래 샘플). 그런 다음 npm install을 실행합니다. 종속성을 설치하고 npm run dev를 실행하려면 앱을 실행합니다.

TMDB_API_KEY=KEY_GOES_HERE
REDIS_CONNECTION=CONNECTION_GOES_HERE

런타임 시 process.env['TMDB_API_KEY']를 사용하여 이러한 값에 액세스할 수 있습니다. 또는 process.env['REDIS_CONNECTION'] . 값은 .env에서 읽습니다. hooks.ts에서 dotenv를 사용하는 파일 .

Redis에서 API 응답 캐싱

기존 프로젝트에서 개선할 수 있는 한 가지는 Redis에서 개별 영화에 대한 API 응답을 캐시하는 것입니다. 현재 앱은 영화 페이지가 로드될 때마다 TMDB API에 요청합니다. 처음 요청할 때 TMDB에서 데이터를 계속 요청할 필요가 없도록 API 응답을 Redis에 저장할 수 있습니다.

TMDB API는 빠르며 반드시 캐시할 필요는 없지만 응답하는 데 시간이 걸리거나 요청 제한이 있는 API에 유용한 기술이 될 수 있습니다. 또한 API가 다운될 경우 탄력성을 제공합니다.

이미 ioredis를 종속성으로 추가했습니다. 이것은 Redis 데이터베이스와 상호 작용하는 데 도움이 되는 Node용 Redis 클라이언트입니다.

src/lib/redis.ts 파일 생성 . 이 파일은 Redis 클라이언트를 초기화하고 다른 기능에서 사용할 수 있도록 내보냅니다. 또한 키를 가져오기 위한 몇 가지 도우미 기능이 포함되어 있습니다. 파일에 아래 코드를 추가하세요.

import Redis from "ioredis";

const connectionString = process.env["REDIS_CONNECTION"];

export const MOVIE_IDS_KEY = "movie_ids";

/** Return the key used to store movie details for a given ID in Redis */
export function getMovieKey(id): string {
  return `movie:${id}`;
}

export default connectionString ? new Redis(connectionString) : new Redis();

이 구현은 서버리스 인스턴스당 단일 Redis 클라이언트를 생성합니다. 또 다른 옵션은 요청별로 Redis 연결을 만들고 닫는 것입니다. Upstash에는 Redis 연결을 초기화할 필요가 없는 REST API도 있습니다. 가장 좋은 옵션은 앱의 지연 시간과 메모리 요구 사항에 따라 다릅니다.

/src/routes/movie/[id].json.ts로 이동합니다. 파일. Redis 클라이언트 및 getMovieKey 가져오기 redis.ts의 함수 .

import redis, { getMovieKey } from "$lib/redis";

getMovieDetailsFromApi를 살펴보세요. 기능. TMDB API를 호출하여 영화 세부 정보와 크레딧을 가져와 반환합니다. 데이터를 반환하기 전에 Redis 캐시에 저장하여 다음에 API를 호출하는 대신 캐시된 버전을 검색할 수 있도록 합니다. 새 cacheMovieResponse 추가 Redis에서 데이터를 캐시하기 위해 파일에 함수를 추가합니다.

async function cacheMovieResponse(id: number, movie, credits) {
  try {
    const cache: MovieDetails = {
      movie,
      credits,
    };
    // store movie response for 24 hours
    await redis.set(getMovieKey(id), JSON.stringify(cache), "EX", 24 * 60 * 60);
  } catch (e) {
    console.log("Unable to cache", id, e);
  }
}

각 영화 ID는 다른 키에 저장됩니다. 예를 들어 ID가 11인 영화에 대한 정보를 검색하는 경우 키는 movie:11입니다. . redis.set에 대한 마지막 두 인수 24시간(86400초) 동안만 데이터를 캐시해야 한다고 말합니다. 데이터를 영구적으로 캐시해서는 안 됩니다. 사용 약관을 위반하고 데이터는 결국 오래될 것입니다.

캐시에 저장하기 전에 JS 객체를 문자열화해야 합니다. 또한 엔드포인트를 보다 탄력적으로 만들기 위해 이 작업 중에 발생한 모든 예외를 포착합니다. 데이터를 캐시할 수 없는 경우 처리되지 않은 예외를 throw하는 대신 API에서 데이터를 반환해야 합니다.

이제 새로운 cacheMovieResponse를 사용할 수 있습니다. getMovieDetailsFromApi 내부의 함수 API 응답을 캐시에 저장합니다.

async function getMovieDetailsFromApi(id: number) {
  const [movieResponse, creditsResponse] = await Promise.all([
    getMovieDetails(id),
    getCredits(id),
  ]);
  if (movieResponse.ok) {
    const movie = await movieResponse.json();
    const credits = await creditsResponse.json();

    // add this line
    await cacheMovieResponse(id, movie, credits);

    return {
      movie,
      credits,
    };
  }

  return {
    status: movieResponse.status,
  };
}

이제 캐시에 데이터를 저장했지만 여전히 캐시된 데이터를 검색해야 합니다. 캐시에서 영화 정보를 읽는 기능을 추가해 보겠습니다.

async function getMovieDetailsFromCache(
  id: number
): Promise<MovieDetails | Record<string, never>> {
  try {
    const cached: string = await redis.get(getMovieKey(id));
    if (cached) {
      const parsed: MovieDetails = JSON.parse(cached);
      console.log(`Found ${id} in cache`);
      return parsed;
    }
  } catch (e) {
    console.log("Unable to retrieve from cache", id, e);
  }
  return {};
}

데이터가 문자열로 저장되었기 때문에 사용할 수 있도록 개체로 구문 분석해야 합니다. 이전 함수와 유사하게 예외를 기록하지만 캐싱 함수를 탈출하는 것을 허용하지 않습니다. 우리는 항상 API에서 데이터를 검색하는 것으로 대체할 수 있습니다.

마지막으로 메인 요청 핸들러에서 캐싱 기능을 호출할 수 있습니다. 캐시에서 데이터를 찾으면 즉시 반환합니다. 그렇지 않으면 이전과 같이 API에서 읽습니다.

export const get: RequestHandler = async function ({ params }) {
	const { id: rawId } = params;
	// validate and sanitize the input
	const id = parseInt(rawId);
	if (isNaN(id)) {
		return {
			status: 400
		};
	}

	// add these lines
	const { movie, credits } = await getMovieDetailsFromCache(id);
	if (movie && credits) {
		return {
			body: adaptResponse(movie, credits)
		};
	}

	// fallback to the API
	const result = await getMovieDetailsFromApi(id);

데모 리포지토리에서 이 끝점에 대한 최종 코드를 볼 수 있습니다.

이러한 변경 사항을 적용하여 영화 페이지로 이동해 보십시오. 페이지를 새로고침하면 콘솔 로그에 "캐시에서 ID 발견"이 표시되어 해당 영화를 캐시에서 성공적으로 저장하고 검색했음을 나타냅니다.

무작위 영화 검색

Redis는 API 응답을 캐싱하는 것 이상을 수행할 수 있습니다. 사용자를 임의의 영화로 리디렉션하는 경로를 구축하는 방법을 살펴보겠습니다.

이것은 1에서 300000 사이의 임의의 숫자를 선택하고 이를 영화 ID로 사용하는 것만큼 간단하지 않습니다. 해당 범위의 모든 숫자가 영화에 해당하는 것은 아닙니다. 예를 들어 ID가 1 또는 1000인 영화는 없습니다. 가능한 최대 ID가 무엇인지 추적하는 것도 까다로울 것입니다. 새로운 영화가 추가됩니다. 대신 2단계 프로세스를 사용하여 임의의 영화를 선택합니다.

  1. 검색 쿼리가 수행되면 반환된 모든 ID를 Redis Set에 배치합니다.
  2. /movie/random 경로가 요청되면 해당 세트의 임의 구성원을 검색하고 해당 영화 세부 정보 페이지로 리디렉션합니다.

반환될 수 있는 임의의 영화는 작게 시작하지만 더 많은 검색이 수행될수록 더 커집니다.

무작위 세트를 채우려면 /src/routes/search.json.ts를 업데이트하십시오. 다음으로.

import type { RequestHandler } from "@sveltejs/kit";
import type { SearchResponse } from "$lib/types/tmdb";
import redis, { MOVIE_IDS_KEY } from "$lib/redis";

const VOTE_THRESHOLD = 20;

export const get: RequestHandler = async function ({ query }) {
  const searchQuery = query.get("query");
  const page = query.get("page") ?? 1;
  const response = await fetch(
    `https://api.themoviedb.org/3/search/movie?api_key=${process.env["TMDB_API_KEY"]}&page=${page}&include_adult=false&query=${searchQuery}`
  );
  const parsed: SearchResponse = await response.json();

  // add these lines
  const filteredMovies = parsed.results.filter(
    (movie) => movie.vote_count >= VOTE_THRESHOLD
  );
  if (filteredMovies.length > 0) {
    try {
      await redis.sadd(MOVIE_IDS_KEY, ...filteredMovies.map((r) => r.id));
    } catch (e) {
      console.log(e);
    }
  }

  return {
    body: parsed,
  };
};

모든 영화가 세트장으로 돌아왔다. 투표가 많지 않은 영화는 사용자가 심사할 가능성이 적기 때문에 필터링하기로 했습니다. VOTE_THRESHOLD를 조정할 수 있습니다. 당신의 취향에.

이 변경으로 영화를 검색하면 영화 ID 세트가 채워지기 시작합니다. 일부 ID를 세트에 추가하려면 검색을 수행하십시오. 예:

  • 스타워즈
  • 라이온 킹
  • 스파이더맨

몇 가지 검색을 수행한 후 Redis에 저장된 임의의 ID 세트가 있어야 합니다. /movie/random에 대한 경로를 생성해 보겠습니다. 끝점 및 페이지.

src/routes/movie/random.json.ts

import type { RequestHandler } from "@sveltejs/kit";
import redis, { MOVIE_IDS_KEY } from "$lib/redis";

export const get: RequestHandler = async function () {
  const randomId = await redis.srandmember(MOVIE_IDS_KEY);
  return {
    body: randomId,
  };
};

이 서버 엔드포인트는 SRANDMEMBER를 사용하여 영화 ID 세트에서 임의의 ID를 선택합니다.

src/routes/movie/random.svelte

<script context="module" lang="ts">
  import type { Load } from "@sveltejs/kit";

  export const load: Load = async function ({ fetch }) {
    const result = await fetch(`/movie/random.json`);
    if (result.ok) {
      const id = await result.json();
      return {
        redirect: `/movie/${id}`,
        status: 303,
      };
    }

    return {
      status: result.status,
      error: new Error("Could not retrieve random id"),
    };
  };
</script>

해당 Svelte 페이지는 UI를 렌더링하지 않으므로 로드 기능만 필요합니다. 방금 추가한 서버 엔드포인트를 호출하고 해당 영화 페이지로 리디렉션합니다.

그게 다야! 이제 https://localhost:3000/movie/random으로 이동합니다. 이전에 수행한 검색에서 임의의 영화로 자동 리디렉션되어야 합니다. 이 경로에 더 쉽게 액세스할 수 있도록 /src/routes/__layout.svelte의 탐색에 추가할 수 있습니다.

<header>
  <nav>
    <a href="/">Search</a>
    <a href="/movie/random">Random</a>
  </nav>
</header>

라이브 데모에서 작동하는 것을 볼 수 있습니다.

마무리

Redis를 사용하는 더 많은 방법이 있지만 이 게시물을 통해 SvelteKit 앱에서 Redis를 통합하는 기본 사항을 잘 이해하셨기를 바랍니다. 최종 코드는 GitHub에서, 라이브 데모는 Netlify에서 볼 수 있습니다.

질문이 있으신가요? 트위터에 연락하세요. 내 블로그에서 Svelte에 대한 다른 글을 찾을 수도 있습니다.