お疲れ様です、ラスカルです。
私は仕事でバックエンドもフロントエンドも担当しているのですが、実装した両者をGraphqlを使って繋ごうとしたときに次のエラーに遭遇しました。
Access to XMLHttpRequest at 'http://localhost:4000/graphql'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
ふむ、どうやらフロント側のリクエストに対して、バックエンドが何やらCORSなる規約に則って、
「おい、リクエストはちゃんと決まりを守って投げてこい🤬」
とお怒りの様子です🤔
##状況説明
###バックエンド
バックエンドはNode.jsのExpressを使って、TypeScriptで実装しました。
自社AWSに繋がっていて、画像やらデータやらをリクエストに応じて返すことができます。
リクエストはGraphQLで受け取るように実装していて、実際ローカル環境でもちゃんとレスポンスが期待通りに返ってくることも確認できていました。
このとき、ポートは4000、つまりlocalhost:4000/graphqlがバックエンド側のURIでした。
###フロントエンド
フロントエンドはReactとTypeScriptで実装していて、最初の読み込みの際にGraphQLを使って、バックエンドにリクエストを投げ、受け取ったレスポンスの内容を表示するという至ってシンプルな実装です。
このとき、ポートは3000、つまりlocalhost:3000/がフロントエンド側のURIでした。
エラーの原因
エラーメッセージにも書いてありますが、要はCORSという決まり的にNGなリクエストの投げ方をしてしまったために、エラーになっているだけです。
CORSの詳細は一旦置いといて、簡単にまとめると違うドメインからのリクエストは、ブラウザを介して受け付けられませんよという決まりです。
確かに
- フロントエンドはlocalhost:3000/
- バックエンドはlocalhost:4000/
と異なるドメインになっているため、CORSに引っかかってしまったんですね。
まぁ考えれば当たり前ですが、違うドメインからのアクセスができてしまう方がセキュリティ的にはよろしくないですよね。
ありがとう、CORS🙏
解決策
前置き長くなりましたね。
結論としては、corsなるnode_moduleをインストールすれば解決です。
npm install --save cors
さらにTypeScriptを使っている方は、
npm install --save-dev @types/cors
をインストールしておきましょう。
終わったら、バックエンドの実装に入ります。
実装といっても、app.use(cors())
を追加するだけ。
const app = express()
++ app.use(cors())
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}))
app.listen(4000, () => {
console.log('Start Express GraphQL Server. Listen to localhost:4000/graphql')
});
以上でエラーは解決すると思います。
ただこちらの実装だと、どのドメインからでもアクセスできてしまうため、(ローカルホストだからあんま影響ないとは思うけど、後々のために)特定のドメインからだけリクエストを受け付けられるように実装します。
const app = express()
++ const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200
}
++ app.use(cors(corsOptions))
-- app.use(cors())
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}))
app.listen(4000, () => {
console.log('Start Express GraphQL Server. Listen to localhost:4000/graphql')
});
許可するドメインと成功次のステータスコードが設定されているcorsOptionsなる変数を作成。
それをcors()の引数で渡してあげればOKです👏
おまけ:解決しなかった対処法
CORS関連でいろいろ調べていると、corsモジュールをわざわざインストールせずとも、
const app = express()
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}))
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
res.header('Access-Control-Max-Age', '86400');
next();
});
app.listen(4000, () => {
console.log('Start Express GraphQL Server. Listen to localhost:4000/graphql')
});
のようにヘッダーの上書きを施せばOK的なのを散見したのですが、私の環境ではうまく行きませんでした。
もう少し調べて、わかり次第追記したいと思います🖖