この記事について
この記事はudemyで公開されている講座、Daiz Academyさんの 最短・最速で学ぶ GraphQL Fullstack 実践入門 - Node & React編をやってみての感想やつまづいたところまとめです。
(Daiz Academyさんの講座にはいつもお世話になっています!🙇♀️)
この記事では、使用技術・言語に関する説明は割愛します。
本記事で記載しているパッケージのバージョンなどは、2022/06/22時点のものです。
使用技術
- GraphQL, Apollo
- React
- mongoDB
など
ディレクトリ構成
※ ディレクトリ構成やファイル名は本家と一部異なります。
※ clientディレクトリ配下はsrc/*以下の使用したもののみ表示しています。
working dir
├── client
├── src
│ ├── App.css
│ ├── App.js
│ ├── components
│ │ ├── Header.js
│ │ ├── MovieList.js
│ │ └── sideNav
│ │ ├── AddDirectors.js
│ │ ├── AddMovies.js
│ │ └── Base.js
│ ├── index.css
│ ├── index.js
│ ├── mutation
│ │ └── mutations.js
│ ├── query
│ │ └── queries.js
├── server
├── app.js
├── models
│ ├── base.js
│ ├── director.js
│ └── movie.js
├── package-lock.json
├── package.json
└── schema
├── director.js
├── movie.js
└── schema.js
package.json
- client
"dependencies": {
"@apollo/client": "^3.6.8",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"bootstrap": "^5.1.3",
"graphql": "^16.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.32.2",
"react-scripts": "5.0.1",
"reactstrap": "^9.1.1",
"web-vitals": "^2.1.0"
}
- server
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.1",
"express-graphql": "^0.12.0",
"graphql": "^15.8.0",
"mongoose": "^6.3.8"
}
server/client側つまづいたこと・気になったこと
- ファイル分割がほとんどないです。(client/serverどちらも)
- 特にserver側ですが、modelsやschemaでディレクトリを設けるものの、それぞれの配下にはmodel.jsやschema.jsのファイル一つで講座は進んでいきます。設計思想まで言及するのは講座でやりたいことではないとはいえ、ファイル一つが長くなるのはなんだかなあと思ってしまいました。
- ⚠️注意⚠️
- ただ独自にファイルを分割してみたところ、schemaに関してはファイルを分けすぎると循環参照が起こってしまい、client側のコンパイルがうまくいかず画面が落ちてしまいます。(参考例はserver/schema/*へ)
- JSX内での、取得データがundefinedだった時の処理(client)
- queryで取得したデータをmapして表示する実装がありますが、取得データがundefinedだった時の処理についての言及が少なかったように思います。(参考例はclient/stc/compinent/MovieList.jsへ)
- マスタデータがない(server)
- movieテーブルのカラムの一つに
genre
というのがあるのですが、それはgenreId
にしてgenre
というマスタデータを設けても良かったのかもしれないです。勉強がてら改良しても楽しそうですね。
- movieテーブルのカラムの一つに
server/schema/*
ファイルを分割した場合の、この書き方だと、
・ movieに紐づくdirectorは取れる けれど、
・ directorに紐づくmovieが取れなく なってしまっています。
(filedで分ける以外の方法を思いつければよかったですが、、><)
# この部分について
├── server
└── schema
├── director.js
├── movie.js
└── schema.js
// 一部抜粋
const { GraphQLSchema, GraphQLObjectType } = require('graphql');
const movieSchema = require('./movie')
const directorSchema = require('./director');
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
getMovie: movieSchema.getMovieField,
getDirector: directorSchema.getDirectorField,
}
})
const schema = new GraphQLSchema({
query: RootQuery,
})
module.exports = schema;
const {
GraphQLObjectType,
GraphQLID,
GraphQLString,
GraphQLList,
GraphQLNonNull,
} = require('graphql');
const Movie = require('../models/movie')
const Director = require('../models/director')
const { DirectorType } = require('./director')
const MovieType = new GraphQLObjectType({
name: 'Movie',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
genre: { type: GraphQLString },
directorId: { type: GraphQLID },
director: {
type: DirectorType,
resolve(parent, args) {
return Director.findById(parent.directorId)
}
}
})
})
// Query
const getMovieField = {
type: MovieType,
args: {
id: { type: GraphQLString }
},
resolve(parents, args) {
return Movie.findById(args.id)
}
}
module.exports = {
// type
MovieType,
// query
getMovieField,
};
const {
GraphQLObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLID,
GraphQLString,
GraphQLInt,
} = require('graphql');
const Director = require('../models/director')
// FIXME: 循環参照が起こるのでコメントアウト
// const Movie = require('../models/movie')
// const { MovieType } = require('./movie')
const DirectorType = new GraphQLObjectType({
name: 'Director',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
age: { type: GraphQLInt },
// FIXME: 循環参照が起こるのでコメントアウト
// movies: {
// type: new GraphQLList(MovieType),
// resolve(parent, args){
// return Movie.find({ directorId: parent.id })
// }
// }
})
})
// Query
const getDirectorField = {
type: DirectorType,
args: {
id: { type: GraphQLString }
},
resolve(parents, args) {
return Director.findById(args.id);
}
}
module.exports = {
// type
DirectorType,
// query
getDirectorField,
};
client/stc/compinent/MovieList.js
data &&
とdirector?.name
に結構つまづいてしまいました。
strict modeで実装していた訳でもなかったのでちょっと注意が必要かと思います。
// 一部抜粋
<tbody>
{
data && data.getMovies.map(({ id, name, genre, director }) => (
<tr key={ id }>
<td>{ name }</td>
<td>{ genre }</td>
<td>{ director?.name }</td>
<td><Button color='primary' onClick={() => { handleDeleteMovie(id) }}>削除</Button></td>
</tr>
))
}
</tbody>
その他
その他気になったことをつらつらと並べていきます。
現場での開発経験しての違和感など、あくまで私感であり、講座の本筋からは逸れることはご了承ください。
- .envファイルを設けたほうがいいなと思いました。
- 業務での開発では環境変数をまとめた、場合によっては環境ごとの.envファイルを設けるかと思います。mongodbへのアクセスURIなどserver/app.jsに直書きのままなので違和感を覚えました。
- Apollo
- version
- clientからserverに接続するにあたって使用するApolloですが、動画内で使用しているversionやpackageが最新のものとは異なります。動画内ではおそらくv2を使っているようですが、最新はv3なので私はそちらを使用しました。
- 実装
- 公式サイトに見本のコードがあるので特に困ることはないです。v2とv3で書き方が大きく逸れることはなかったので動画見て公式サイト参考にして、で問題なかったです。
- version
- chrome拡張機能
- 動画では、GraphiQLというのを使いますが(localhost:4000でアクセスするやつ)、chromeの拡張機能である、Apollo Client Devtoolsを使っても良さそうです。queryとか書くのこちらの方が楽です。
所感
- 「動画見る→実際に手を動かす→動画内のコードを写経する・google先生に頼る・自力で進める」がしやすいです。
- 自分が今までの開発経験を通しての違和感などを持ってしまうので、上記のような気になることが出てきますが、何かしら動くものを作るという観点からすると、1本あたりの動画の時間も短く簡潔なので、手を動かしながら勉強するには最適でした。
- どんな人向けか
- 言語や技術に関する説明は割愛されているので、ある程度の知見を持った人向け or その辺は自分で調べてカバーする人向けだなという印象です。これらの技術で何かを作ってみたい、という人には最適かと思います。
- また、CRUDの処理を学べるので、自分のアイディアを追加実装することもできる開発物ができます。
- 総じてすごく勉強になりました!