GraphQLサーバをつくる(heroku + Express + Apollo Server + Prisma)
概要
GraphQLサーバを作る
分からんメモ
- Node.jsを利用できるCDN?サーバ?ってどこだ
ChatGPTに聞いたら
- Heroku
- AWS Elastic Beanstalk
- Google App Engine
- Microsoft Azure App Service
を教えてもらった
Heroku使ってみる
来週はここから
Heroku CLIをインストール
npm install -g heroku
以下でlogin実行
heroku login
アプリ作成
heroku create
なんか怒られた
Creating app... !
▸ To create an app, verify your account by adding payment information.
▸ Verify now at https://heroku.com/verify Learn more at
▸ https://devcenter.heroku.com/articles/account-verification
アプリを作成しています... !
▸ アプリを作成するには、支払い情報を追加してアカウントを確認します。
▸ https://heroku.com/verify で今すぐ確認 詳細はこちら
▸ https://devcenter.heroku.com/articles/account-verification
カード情報を登録後、再度 heroku createでいけったぽい
以下が丁寧で読んでて分かりやすい。
expressやts周りも含めて明日以降で挑戦してみよう
かしこか
上の記事を参考に、nodemon, ts, express, apollo-serverなどを導入し色々整えて、
ローカルでseverを立ち上げられる所まで来た
つぎはここから
rimrafインストール
pnpm i -D rimraf
package.jsonのscriptsに以下を追加
"build": "rimraf ./build && tsc",
"start": "npm run build && node ./build/index.js",
touch Procfileを作成(heroku側で実行されるファイル)
以下を記述
web: npm start
heroku デプロイ
コードのデプロイ
アプリを Heroku にデプロイするには。次のように git push コマンドを使用して、ローカルリポジトリのメインブランチから heroku リモートにコードをプッシュします。次に例を示します。
git push heroku main
(ログインができていない場合は、heroku login)
途中でエラーが発生してる
remote: Resolving node version 18.x...
remote: Downloading and installing node 18.16.0...
remote: Using default npm version: 9.5.1
1.localのnode, npmバージョンとことなっている
2.以下のts系でエラーが出ている
remote: > rimraf ./build && tsc
remote:
remote: error TS6059: File '/tmp/build_8524c435/vite-project/src/App.tsx' is not under 'rootDir' '/tmp/build_8524c435/src'. 'rootDir' is expected to contain all source files.
remote: The file is in the program because:
remote: Matched by default include pattern '**/*'
remote: error TS6059: File '/tmp/build_8524c435/vite-project/src/main.tsx' is not under 'rootDir' '/tmp/build_8524c435/src'. 'rootDir' is expected to contain all source files.
remote: The file is in the program because:
remote: Matched by default include pattern '**/*'
remote: error TS6059: File '/tmp/build_8524c435/vite-project/vite.config.ts' is not under 'rootDir' '/tmp/build_8524c435/src'. 'rootDir' is expected to contain all source files.
remote: The file is in the program because:
remote: Matched by default include pattern '**/*'
vite-projectを移動したらデプロイが通った!
vite側の設定に問題があるっぽい
herokuでデプロイ整理
- package.jsonのengineに記載があるバージョンのnode, npmでnpm iが実行される(engineの記載がない場合は、ltsバージョンでインストールが実行される)
- node_modulesが作られ次第、Procfileに記載しているコードが動作する
- 現状
npm run build && node ./build/index.js
が実行される為、./build/index.jsがサーバとして動作する様にlocalで組めてればok
ここまでが整えられた状態でherokuのmainリポジトリにプッシュ
git push heroku main
heroku logsでherokuアプリのログが確認できる
heroku logs --tailで監視
↓の様なエラーが表示される
2023-04-27T02:54:14.795435+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=afternoon-shore-82089.herokuapp.com request_id=ab4d9fad-c8f1-4b22-99f1-93fb41b37ad5 fwd="218.220.224.82" dyno= connect= service= status=503 bytes= protocol=https
こんな状況
やりたいこと整理
- GraphQLサーバをたてる
- CORS許可する(様子見て)
- ローカルでvite+React環境作成
- ローカルからGraphQLサーバへアクセスする
Node+express構成をherokuへデプロイできれば、1はクリアできるはずなので調整
見てる
一旦、/でアクセスして、hello,worldを表示させてみる
下記が表示されているので確認中
2023-05-08T02:40:01.716426+00:00 heroku[web.1]: Process exited with status 127
2023-05-08T02:40:01.766671+00:00 heroku[web.1]: State changed from starting to crashed
sh: 1: rimraf: not found
rimrafがない
トラップ
原因②:package.jsonにdevDependenciesの記載がある
devDependenciesはデバッグ時に追加で読み込むライブラリの定義ですが、これがあるとherokuではまともに動作しないようです。
第一関門クリア
一旦、GraphQLサーバと疎通できるかどうかをlocalでprojectをたてて確認してみる
viteでサクッとプロジェクト作って以下を記述
App.tsx
import { ApolloClient, InMemoryCache, gql } from '@apollo/client'
function App() {
const client = new ApolloClient({
uri: 'https://○○/api',
cache: new InMemoryCache(),
})
const clickHandler = () => {
client
.query({
query: gql`
query Query {
hello
}
`,
})
.then((result) => console.log(result))
}
return (
<div>
<p>ボタンclickするとGraphQLサーバと通信するよ</p>
<button onClick={clickHandler}>click</button>
</div>
)
}
export default App
出来た
次はheroku + prisma
元々はMySQLでやろうと思ってたけど、ポスグレ系のサンプルが多い為、そっちに合わせてみる
Supabaseの内部DBがPostgresで出来ていて、元々参考にしていたサイトにSupabaseのサンプルが書かれてあった
herokuでDBを扱う場合パターンが2つ
- herokuのDataでDB設定を行う
- Supabase等の外部DBと接続する
どちらもPrismaを利用していれば、scheme.prismaのdatasourceを変更するだけになりそう
とりあえずherokuのDataでDB設定を行う」のパターンを試す
つぎはここから
heroku上でenvを設定する方法
DBとバックエンド間の連携にprisma/clientを使う
2023/05/16やること
- GraphQLのクエリを整理する
- PrismaとGraphQL間を接続する
- ReactとGraphQL間を接続する(CRUD的な部分)
以下参考
- Query, Mutationのschemeは既に記述している
- Queryに対するresolversを一旦整理する
- resolversは一旦ローカルのtsファイルを利用する
- 上記が出来たらPrismaと接続する
アーキテクチャばっかり組んでいて肝心のクエリの書き方を忘れている・・・
クエリ実行時の戻り値がプリミティブな場合、プロパティを書くだけで良い
query Query {
hello
}
クエリ実行時の戻り値がオブジェクトな場合、Queryに記述したプロパティ + 取得対象のオブジェクトの中で必要なプロパティを記述
query Query {
books {
id
title
}
}
クエリ実行出来た
typeDefsに記述したスキーマの定義が改めて理解できた
resolversの整理もできたし、それぞれresolversに記述するタイプも理解できた
リゾルバに詳細なタイプの記述ができていない場合、
定義しているクエリ内でオブジェクトを操作する形の記述を行うことになる。
GraphQLを利用している場合、オブジェクトを組み換えて、入れ子構造を作成することは望ましくない為、
入れ子構造が発生した場合は、リゾルバタイプとして切り出すのが良い
つぎ以下から
input キーワードの利用
Queryで、filterを利用してinput typeのスキーマを接続することが出来る
books(filter: BooksInput): [Book!]!
リゾルバ側の実行文を以下で記述
books: (_: any, { filter }: argFilterType) => {
let filteredBooks = books
if (filter?.isRead) {
filteredBooks = filteredBooks.filter((book) => {
return book.isRead
})
}
return filteredBooks
},
つぎこれ
context, mutationなど理解が進んだ
残は以下
↓は前にやってた
↓これ
メインのコードは以下だけみたい。
ApolloServerのcontextプロパティにPrismaClientのインスタンスを渡すだけ
import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()
const apolloServer = new ApolloServer({
typeDefs,
resolvers: {
Query,
Mutation,
Category,
Book,
},
// context: {
// books,
// categories,
// },
context: {
prisma,
},
})
型定義
export type Context = {
prisma: PrismaClient<Prisma.PrismaClientOptions, never, Prisma.RejectOnNotFound | Prisma.RejectPerOperation>
}
scheme.prismaで定義した型の通りにtypeDefsの型を合わせていく
resolverをprismaに置き換える
そういえばMutationのdelete, updateを作り込んでない
とりあえずPrismaとGraphQLのシンプルな疎通ができた!
つぎこれ
Queryに記述する関数の第1引数がparent, 第2引数がQueryメソッドのfilterで渡される値, 第3引数がcontext
books: (_: any, { isRead }: { isRead: boolean }, { prisma }: Context) => {
return prisma.book.findMany({
where: {
isRead,
},
})
},
GraphQLのクエリに引数をもたせる場合の書き方
query Book {
books(isRead:true) {
id
title
isRead
}
}
GraphQLメソッドメモ
複数一致
findMany
単一一致
findUnique
外部から引数を受け付ける場合
query Book($bookId: Int!) {
book(id: $bookId) {
id
title
isRead
}
}
{
"bookId": 1
}
return books.filter((book) => book.categoryId === id)
ここの解決から
// Category側のidとBook側のcategoryIdの紐づけ
export const Category = {
books: ({ id }: { id: number }, _: any, { prisma }: Context) => {
return prisma.book.findMany({
where: {
categoryId: id,
},
})
},
}
mutation ok
delete
deleteBook: async (_: any, { id }: { id: number }, { prisma }: Context): Promise<BookPayload> => {
const book = await prisma.book.findUnique({
where: {
id,
},
})
if (!book) {
return {
errors: [{ message: '本が見つかりませんでした' }],
book: null,
}
}
await prisma.book.delete({
where: {
id,
},
})
return {
errors: [],
book,
}
},
}
update resolve
updateBook: async (
_: any,
{ id, input }: { id: number; input: MutationBook['input'] },
{ prisma }: Context
): Promise<BookPayload> => {
const book = await prisma.book.findUnique({
where: {
id,
},
})
if (!book) {
return {
errors: [{ message: '本が見つかりませんでした' }],
book: null,
}
}
const updatedBook = await prisma.book.update({
where: {
id,
},
data: {
...input,
},
})
return {
errors: [],
book: updatedBook,
}
},
useQueryにGraphQLのクエリを投げたら簡単に取れた!
import { gql, useQuery } from '@apollo/client'
const Component = () => {
const booksData = gql`
query allBooks {
books(isRead: true) {
id
title
author
}
}
`
const { data } = useQuery(booksData)
}