Next.js 및 Firebase Hosting SWR을 사용합니다.그나저나 GraphiQL과 Cloud Run
개요
며칠 전 넥스트.저는 js를 Firebase Hosting + Cloud Function For Firebase로 디자인해 봤어요.
왜냐하면 당분간 방문하지 않을 때 방문하면 상당히 엄격하거든요.
Firebase Hosting+SWR로 서버측 처리가 없으면 개발이 안 되는지 해봤어요.
GraphiQL(gqlgen)으로 뒷면의 API를 제작하는 김에 Terraform을 통해 클라우드 런에 디자인하기
다 쓰면 길어질 수 있으니 나중에 반복할 수 있도록 요점을 미리 적어두세요
로컬 환경
지금까지 비즈니스에서 프런트엔드와 백엔드(API)는 모두
왜냐하면 저는 같은 창고에 저장된 상황만 겪었거든요.
이번 실험적 으로 창고 를 분리하여 각각 독립해 보았다
백엔드(API)
GraphiQL(gqlgen) 만들기
업무상 REST만 접했지만 개인적으로는 그래피QL의 지식 수준을 향상시키고 싶으니 그렇게 하자
이것저것 돌아다니는 gqlgen이 편할 것 같아서 이걸로 적당한 API를 만들었어요.
상세히 기재되지는 않았지만 대체로 이렇게 만들었다
local
go mod init github.com/shintaro-uchiyama/xxx
go get github.com/99designs/gqlgen
go run github.com/99designs/gqlgen init
schema.graphqls
의 패턴을 원하는 것으로 변경gqlgen을 통해 코드 다시 생성
local
go run github.com/99designs/gqlgen generate
resolver.go
응답하는 struct 지정schema.resolvers.go
에 가상 응답 미리 쓰기※ 파이어스토어 근처 DB에서 획득
docker-compose를 통해 로컬 환경 만들기
GraphiQL을 움직이기 위한 구글 Docker 준비
build/api/Dockerfile
FROM golang:1.15.6 AS local
WORKDIR /go/src/app
RUN go get github.com/cespare/reflex
CMD reflex -r '(\.go$|go\.mod)' -s go run server.go
docker-compose로 이 녀석을 깨워라docker-compose.yml
version: '3.8'
services:
api:
build:
context: ./build/api
target: local
ports:
- "8080:8080"
volumes:
- ./:/go/src/app
방문http://localohst:8080
이런 느낌으로 GraphiQL 콘솔이 나오면 OK!조회를 실행하고 결과를 얻은 후 마치 일하는 것 같다🧔
완전한 수다(GraphiQL의 Group By)
"지난주 분류별 총 매출액을 차트에 표시하고 싶어요".
GraphiQL에서 SQL이 말하는 Group By(Aggregation)는 어떻게 표현됩니까?
이런 느낌의 Group By의 집계 검색어를 어떻게 쓸까 한두 시간 고민했어요.
SELECT category_id, date, SUM(quantity) FROM sales WHERE date > DATE_SUB(DATE_TRUNC(CURRENT_DATE('Asia/Tokyo'), week), INTERVAL 1 week) GROUP BY category_id, date
issue에서 보듯이 프로그램 내의 함수로 복잡한 통계를 진행할 필요가 없다Schema로 표현하기로 했습니다.(의도와 일치하는가
query findCategories {
categories {
id
name
products {
id
}
lastWeekSales{
soldAt
quantity
}
}
}
https://github.com/graphql/graphql-js/issues/855#issuecomment-302413633 프런트엔드
docker-compose를 통해 로컬 환경 만들기
이동가능next 용기 만들기
이번 공연은 본공연 때 Firebase Hosting을 사용했는데, 원래는 node였다.js 컨테이너 아니에요.
정적 출력 파일만 돌려주는 용기를 준비해야 할 것 같아요.
먼저 개발하고 싶어요
next dev
. 그래서 이쪽으로 가세요.docker/nextjs/Dockerfile
FROM node:15.4.0-alpine3.10
WORKDIR /usr/src/app
제작된 Docker file을 docker compose에서 호출docker-compose.yml
version: '3.8'
services:
nextjs:
build: ./docker/nextjs
container_name: nextjs
volumes:
- ./:/usr/src/app
command: "npm run dev"
ports:
- "3000:3000"
networks:
- ucwork-api_default
networks:
ucwork-api_default:
external: true
하나의 점으로 앞의 docker-compose에서api의 docker-compose에 접근할 수 있습니다하고 있는 일
docker network ls
에 나타나는 API의 네트워크를 지정합니다.※ 단, 이번 정식 공연은 노드입니다.js 환경이 없기 때문에, 실제로는 아무런 관계가 없다
※ Proxy를 원한다면 여기서 하는 게 좋을 것 같아요.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
91bf2ecd2671 ucwork-api_default bridge local
SWR을 통한 API 요청
사실 이거 해보고 싶었는데 한참 돌아다녔어요...
환경에 맞게 API URL 변경하기
정말 이런 느낌
/api/proxy/
서로 다른 환경의 URL에서 대리하고 싶지만이번에는 노드다.js 환경이 없어서 포기했어요.
next.config.js
module.exports = {
async rewrites() {
return [
{
source: "/api/proxy/:path*",
destination: `${process.env.API_URL}/:path*`,
},
];
},
};
client 측면에서도 읽을 수 있는 환경 변수로 설정next.config.js
module.exports = {
env: {
API_URL: process.env.API_URL,
}
};
API_URL은 다음과 같이 설정됩니다..env.development
API_URL=http://localhost:8080
npx dev
는 읽기 용이.env.development
swr 실행
이런 느낌으로 Hooks 디렉터리에서 분리해서 swr로 실행합니다.
상품 일람표 가지고 올게요.
hooks/useProducts.ts
import useSWR from "swr";
import { request } from "graphql-request";
import { Product } from "./useProduct";
interface Products {
products: Product[];
isLoading: boolean;
error: any;
}
export const useProducts: () => Products = () => {
const fetcher = (query) => request(`${process.env.API_URL}/query`, query);
const { data, error } = useSWR(
`{
products {
janCode
name
price
registeredAt
}
}`,
fetcher
);
return {
products: data ? data.products : [],
isLoading: !error && !data,
error: error,
};
}
제품 종류는 다른 파일로 정의합니다hooks/useProduct.ts
export interface Product {
janCode: string;
name: string;
price: number;
registeredAt: string;
}
component에서 hooks 호출components/organisms/ProductTable.tsx
import { useProducts } from "../../hooks/useProducts";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
minWidth: 650,
},
})
);
export const ProductTable: React.FC<{}> = () => {
const classes = useStyles();
const [t] = useTranslation();
const { products, isLoading, error } = useProducts();
if (isLoading) {
return <CircularProgress />;
} else if (error) {
return <div>{t("products.table.errors.load")}</div>;
} else {
return (
<TableContainer component={Paper}>
...
<TableBody>
{products.map((row) => {
const registeredAt = new Date(row.registeredAt);
return (
<TableRow key={row.name}>
<TableCell align="right">{row.name}</TableCell>
loading 및 오류 및 데이터를 반환할 때너무 쉬워서 좋아요.캐시된 물건을 사용하기 때문에 매우 빠르다
CORS 대책
오리진http://localhost:8080CORS에 API 요청을 해서 그런가 봐요.
GraphiQL 서버에 CORS 라이센스 설정이 추가되면 위에서 붙여넣은 대로 API 요청을 적절하게 수행할 수 있습니다
※ 모두 허가하긴 했지만, 대상의 URL을 사용해 선별🙇♂️
server.go
package main
import (
"log"
"net/http"
"os"
"github.com/go-chi/chi"
"github.com/rs/cors"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/shintaro-uchiyama/ucwork-api/graph"
"github.com/shintaro-uchiyama/ucwork-api/graph/generated"
)
const defaultPort = "8080"
func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
router := chi.NewRouter()
cors := cors.New(cors.Options{
// FIXME: strict to target urls
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
})
router.Use(cors.Handler)
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
router.Handle("/", playground.Handler("GraphQL playground", "/query"))
router.Handle("/query", srv)
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, router))
}
브라우저에서 작업 확인
앞으로 docker-compose가 세워져
http://localhost:3000
페이지가 표시되어야 합니다!docker-compose up -d --build
자신의 상황은 이렇다정식 절차
백엔드(API)
Cloud Run으로 설계
Docker file 준비
다단계 구축에서 출력을 구축하고 실행하는 파일
build/api/Dockerfile
FROM golang:1.15.6 AS local
WORKDIR /go/src/app
RUN go get github.com/cespare/reflex
CMD reflex -r '(\.go$|go\.mod)' -s go run server.go
FROM golang:1.15.6 AS builder
WORKDIR /go/src/github.com/shintaro-uchiyama/xxx
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app server.go
FROM alpine:3.12.3 AS production
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY /go/src/github.com/shintaro-uchiyama/xxx/app .
CMD ["/root/app"]
Container Registry 디자인
자체 GCP 프로젝트를 위한 설계
docker build --target production -t ucwork-api -f build/api/Dockerfile .
docker tag ucwork-api gcr.io/[project_id]/ucwork-api:0.1.0
docker push gcr.io/[project_id]/ucwork-api:0.1.0
Cloud Run에 대한 디버그
Cloud Run의 module 제작
약간의 검증이 있어서 누구나 볼 수 있도록 인증을 취소합니다
modules/gcp/cloud_run/main.tf
resource "google_cloud_run_service" "default" {
name = "cloudrun-srv"
location = "asia-northeast1"
project = var.project
template {
spec {
containers {
image = "gcr.io/${var.project}/ucwork-api:0.1.0"
}
}
}
traffic {
percent = 100
latest_revision = true
}
}
data "google_iam_policy" "noauth" {
binding {
role = "roles/run.invoker"
members = [
"allUsers",
]
}
}
resource "google_cloud_run_service_iam_policy" "noauth" {
location = google_cloud_run_service.default.location
project = google_cloud_run_service.default.project
service = google_cloud_run_service.default.name
policy_data = data.google_iam_policy.noauth.policy_data
}
모든 프로젝트를 펼칠 수 있도록project_id를 변수로 설정
modules/gcp/cloud_run/variables.tf
variable "project" {
description = "gcpの対象project id"
type = string
}
공식 환경에서 이번 대상의 프로젝트를 사용합니다id 지정environments/production/main.tf
terraform {
required_version = "0.14.3"
}
module "cloud_run_api" {
source = "../../modules/gcp/cloud_run"
project = "[project_id]"
}
설계를 진행하다cd environments/production
terraform init
terraform validate
terraform plan
terraform apply
표시된 Cloud Run에 액세스하는 URLGraphiQL 콘솔이 뜨면 잘 될 것 같아요.
프런트엔드
package.json에 스크립트 추가
package.json
{
...
"scripts": {
"login": "firebase login --no-localhost",
"build": "next build",
"export": "next export",
"deploy": "firebase deploy --only hosting",
Firebase에 로그인
다음 출력을 실행하는 URL을 통해 Google 계정에 로그인합니다.
터미널에 붙여넣기 로그인 성공!
npm run login
Firebase 구성
Firebase deploy 앞에build and export
정적 파일이 out 디렉터리로 출력되기 때문에 디버깅으로 지정합니다
왜냐하면 페이지를 넘기면 Firebase hosting이 404가 돼요.
rewrites의 설정을 기록합니다.스케줄러: 다른 방법이 있어?🤔
firebase.json
{
"hosting": {
"predeploy": "npm run build && npm run export",
"public": "out",
"rewrites": [
{
"source": "/dashboard",
"destination": "/dashboard.html"
},
{
"source": "/products",
"destination": "/products.html"
}
]
}
}
실제 디버깅
디자인된 클라우드 런의 URL을env.제품으로 지정
next start
또는 next export
.env.production은 환경 변수로 간주되는 것 같습니다.env.production
API_URL=https://cloudrun-srv-xxx.run.app
디버그 후 출력된 URL에 액세스하면 로컬에서 볼 수 있는 것처럼 페이지가 표시될 것입니다npm run deploy
SWR 덕분인지 아무튼 빨라 보여요총결산
Firebase Hosting Node만 표시됩니다.js를 사용하지 않으려고 시도하다
부인할 수 없는 것은 억지로 이루어진 느낌이다.왜냐하면 js 기능을 충분히 사용할 수 없는 느낌이 있어서.
저는 이제 버셀에 대해서 솔직히 depro 작전을 해보고 싶어요.😫
Reference
이 문제에 관하여(Next.js 및 Firebase Hosting SWR을 사용합니다.그나저나 GraphiQL과 Cloud Run), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/ucwork/articles/eb4242ba124581텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)