将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察
The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...
ゴール 初期セットアップ 依存関係のインストール それでは始めよう まず、メイン・ディレクトリにtsconfig.jsonを追加する:次に、サーバーの実装用にsrc/server.tsを作成する。次に、ローカル・サーバー用とラムダ用の2つの関数を追加する。リゾルバも型定義もないので、いくつか作る必要がある。最初に、私たちは [...] を望んでいると仮定しよう。
npm init -y
npm install --save typescript graphql aws-lambda @types/aws-lambda
まず tsconfig.json
をメインディレクトリに追加する:
{
"compilerOptions":{
"target":"es5"、
"module": "commonjs"、
"allowJs": true、
"strict": true、
"esModuleInterop": true、
「skipLibCheck": true、
「forceConsistentCasingInFileNames": true。
},
"include":["src/*.ts", "src/**/*.ts", "src/**/*.js"]、
"exclude":node_modules"], "exclude": ["node_modules"].
}
では、次のものを作ってみよう。 src/server.ts
サーバーの実装のために。次に2つの関数を追加する。1つはローカル・サーバー用、もう1つはラムダ用だ。
// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";
const createLambdaServer = () => 以下のようにします。
new ApolloServerLambda({
typeDefs、
resolvers、
introspection: true、
playground: true、
},
});
const createLocalServer = () => 以下のようになります。
new ApolloServer({
typeDefs、
resolvers、
introspection: true、
playground: true、
},
});
export { createLambdaServer, createLocalServer };
さて、リゾルバも型定義もないので、いくつか作る必要がある。まず、ユーザーを作成し、彼らに関する情報を受け取りたいと仮定しよう。
// src/schemas.ts
const { gql } = require("apollo-server-lambda");
const userSchema = gql`
タイプ User {
id: ID!
email:文字列
name: String!
}
型 クエリ
user(id: ID!):ユーザー!
}
型 変異
createUser(name: String!, email: String!, password: String!):ユーザー!
}
`;
ご存じない方も多いだろう、 アポロはとても素晴らしいチュートリアルを用意してくれた
それでは、1つのクエリーと1つの突然変異を持つユーザー・リゾルバを作ってみよう。
// src/resolvers.ts
const userResolver = {.
クエリ:{
user: async (parent, args, context, info) => { {...
{...}
},
},
変異:{
createUser: 非同期 (parent, args, context, info) => { {...
{...}
},
},
};
でもデータがない...修正しよう😉。
型定義とリゾルバをサーバーにインポートすることをお忘れなく。
// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";
import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers";
{...}
さて、データベースとの接続を作成しましょう。今回はMongoDBを使う。無料でメンテナンスも簡単だ。その前に、あと2つの依存関係をインストールしましょう:
npm install --save mongoose dotenv
最初のステップはユーザーモデルを作成することです。
// src/model.ts
import mongoose, { Document, Error, Schema } from "mongoose";
エクスポートタイプ User = ドキュメント & {
_id: string、
email: string、
name: string、
password: string、
};
mongoose.connection.models["User"]を削除します;
const UserSchema:Schema = new Schema({
email:{
型:文字列、
required: true、
unique: true、
},
name: {
type:文字列、
required: true、
minLength: 3、
maxLength: 32、
},
パスワード: {
type:文字列、
required: true、
},
});
export const userModel = mongoose.model ("User", UserSchema);
セキュリティ第一!それでは、パスワードをハッシュ化することで安全性を確保しよう。
npm install --save bcrypt @types/bcrypt
次に、プレミドルウェアのコールバック内にパスワード・セキュリティを実装する。プリミドルウェアの関数は、各ミドルウェアが次に呼び出すときに次々と実行される。パスワードを安全にするために、ソルトとハッシュを別々の関数呼び出しで生成するテクニックを使っている。
// src/model.ts
bcrypt "からbcryptをインポートする;
{...}
const SALT_WORK_FACTOR: number = 10;
UserSchema.pre("save", function (next) {)
const user = this as User;
if (!this.isModified("password")) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) { もし(err)
if (err) return next(err);
bcrypt.hash(user.password, salt, function (err, hash) { もし (err)
if (err) return next(err);
user.password = hash;
next();
});
});
});
{...}
次に、UserSchemaにcomparePasswordsメソッドを追加する:
// src/model.ts
{...}
UserSchema.methods.comparePasswords = function (
candidatePassword: string、
cb: (err: Error | null, same: boolean | null) => void
) {
const user = this as User;
bcrypt.compare(candidatePassword, user.password, (err, isMatch) => { もし(err)
if (err) {
return cb(err, null);
}
cb(null, isMatch);
});
};
{...}
もちろん、ユーザータイプも変更する。
type comparePasswordFunction = (
candidatePassword: string、
cb: (err: Error, isMatch: boolean) => void
) => void;
エクスポートタイプ User = ドキュメント & { { id: string
_id: string、
email: string、
name: string、
password: string、
comparePasswords: comparePasswordFunction、
};
これで、サーバーとデータベース間の接続を手配できる。 MONGODB_URI
は環境変数で、接続を作成するのに必要な接続文字列を格納します。MongoDB atlas アカウントにログインした後にクラスタパネルから取得できます。これを 環境
// .env
mongodb_uri = ...;
このファイルを .gitignore
.素晴らしい!では、データベースとの接続を可能にする関数を追加してみよう。
// src/server.ts
import mongoose, { Connection } from "mongoose";
{...}
let cachedDb:接続;
const connectToDatabase = 非同期 () => { もし(cachedDb)
if (cachedDb) return;
await mongoose.connect(process.env.MONGODB_URI || "", {
useNewUrlParser: true、
useUnifiedTopology: true、
useFindAndModify: false、
useCreateIndex: true、
});
cachedDb = mongoose.connection;
};
{...}
コンテキストは、すべてのリゾルバで共有されるオブジェクトです。これを提供するには、ApolloServerコンストラクタにコンテキスト初期化関数を追加するだけです。やってみましょう。
// src/server.ts
import { userModel } from "./models/user.model";
{...}
const createLambdaServer = async () => 以下のようにします。
new ApolloServerLambda({
typeDefs、
resolvers、
introspection: true、
playground: true、
コンテキスト: async () => {
await connectToDatabase();
return {
モデル:{
userModel、
},
};
},
});
const createLocalServer = () => 以下のようになります。
new ApolloServer({
typeDefs、
resolvers、
introspection: true、
playground: true、
コンテキスト: async () => {
await connectToDatabase();
return {
モデル:{
userModel、
},
};
}
});
おわかりのように、我々はまた ユーザーモデル
を通して コンテキスト
.これでリゾルバを更新できる:
// resolvers.ts
const userResolver = {.
クエリ:{
user: async (_, { email, name, password }, { models: { userModel } }) => {
const user = await userModel.findById({ _id: id }).exec();
return user;
},
},
変異:{
createUser: async (_, { id }, { models: { userModel } }) => {.
const user = await userModel.create({ email, name, password });
user を返します;
},
},
};
いい感じだ!では、基本的なサーバー・インスタンスを作成してみよう:
// src/index.ts
インポート { createLocalServer } from "./server";
require("dotenv").config();
const port = process.env.PORT || 4000;
const server = createLocalServer();
server.listen(ポート).then(({ url }) => { )
console.log(`Server ir running at ${url}`);
});
走行前の最後の仕上げに nodemon.json
{
"watch":["src"]、
"ext":".ts,.js"、
"ignore":[],
"exec":"ts-node --transpile-only ./src/index.ts"
}
スクリプトの追加 package.json
"スクリプト":{
"start":"nodemon"
},
そして走る!
npmスタート
そのような情報はターミナル内で得られるはずだ:
サーバーは http://localhost:4000/ で稼動中
はい」の場合は http://localhost:4000/
をブラウザの中に入れてください。
GraphQLプレイグラウンドが表示されるはずです。新しいユーザーを作成しよう!
データベースでの見え方を見てみよう。
かっこいい!すべてうまくいく!
ユーザー情報を入手してみよう。
パスワード・フィールドを追加する
いいねエラー タイプ "User" のフィールド "password" を照会できません。
.振り返ってみればわかるように、このフィールドはユーザー・タイプ定義の中に追加したものではありません。意図的にそこにあるのです。パスワードやその他の機密データを照会できるようにすべきではありません。
もうひとつ、認証なしでユーザーデータを取得できる。修正する必要がある。
その前に...
GraphQLを使ってみよう コード ジェネレーターを使用して、スキーマに基づいた互換性のあるベース・タイプを取得する。
npm install --save @graphql-codegen/cli @graphql-codegen/introspection
graphql-codegen/typescript @graphql-codegen/typescript-resolvers
作成 codegen.yml
上書き: true
スキーマ"http://localhost:4000"
を生成する:
./src/generated/graphql.ts:
プラグイン
- "typescript"
- "typescript-resolvers"(タイプスクリプト-リゾルバ)
./graphql.schema.json:
プラグイン:
- "introspection"
追加 コドゲン
スクリプトを package.json
"スクリプト":{
"start":"nodemon"、
"codegen":"graphql-codegen --config ./codegen.yml"、
},
そして、ローカル・サーバーが起動したら、スクリプトを実行する:
npm run codegen
成功した場合はメッセージが表示されます:
√ コンフィギュレーションの解析
√ 出力の生成
その情報を受け取ったら、2つのファイルが表示されるはずだ:
Graphql.schema.json
メインディレクトリのgraphql.ts
新しく作成されたパスに src/生成
我々は2番目のものにもっと興味がある。それを開くと、タイプの素晴らしい構造に気づくだろう。
これでレゾルバを改良できる:
// src/resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";
const userResolver:リゾルバ = {
クエリ:{
user: async (_, { id }, { models: { userModel }, auth }):Promise => { { user: async (_ { id })
const user = await userModel.findById({ _id: id }).exec();
return user;
},
},
変異:{
createUser: 非同期 (
_,
{メール、名前、パスワード }、
{ models:{userModel }とする。}
):Promise => { を実行します。
コンストラスト user = await userModel.create({
email、
名前
password、
});
return user;
},
},
};
次に、単純なトークンベースの認証を設定してみよう。
npm install --save jsonwebtoken @types/jsonwebtoken
作成 checkAuth
関数を使用して、トークンが有効かどうかを検証する。結果をコンテキストに追加して、リゾルバ内部でアクセスできるようにします。
// src/server.ts
import { IncomingHttpHeaders } from "http";
import {
APIGatewayProxyEvent,
APIGatewayProxyEventHeaders,
Context,
} from "aws-lambda";
import jwt from "jsonwebtoken";
{...}
const checkAuth = async ({ token } : APIGatewayProxyEventHeaders | IncomingHttpHeaders ) => {
if (typeof token === "string") {
try {
return await jwt.verify(token, "riddlemethis");
} catch (e) {
throw new AuthenticationError(`Your session expired. Sign in again.`);
}
}
};
{...}
const createLambdaServer = async (
{ headers }: APIGatewayProxyEvent,
context: Context
) => {
return new ApolloServerLambda({
typeDefs,
resolvers,
context: async () => {
await connectToDatabase();
const auth = await checkAuth(headers);
return {
auth,
models: {
userModel,
},
};
},
});
};
function createLocalServer() {
return new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: true,
context: async ({ req: { headers } = {} }) => {
const auth = await checkAuth(headers);
await connectToDatabase();
return {
auth,
models: {
userModel,
},
};
},
});
}
また、そのようなトークンを作成する方法も必要である。最良の方法は、リゾルバ内部にログインクエリを実装することである。
// resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";
const userResolver:リゾルバ = {
クエリ:{
user: async (_, { id }, { models: { userModel }, auth }):Promise => { もし(!auth)
if (!auth) throw new AuthenticationError("You are not authenticated");
const user = await userModel.findById({ _id: id }).exec();
user を返します;
},
ログイン: 非同期 (
_,
{email, password }、
{ models:{userModel }とする。}
):Promise => { を実行します。
const user = await userModel.findOne({ email }).exec();
if (!user) throw new AuthenticationError("Invalid credentials");
const matchPasswords = bcrypt.compareSync(password, user.password);
if (!matchPasswords) throw new AuthenticationError("Invalid credentials");
const token = jwt.sign({ id: user.id }, "riddlemethis", { 次のようになります。
expiresIn: 60、
});
return { token };
},
},
変異:{
createUser: 非同期 (
_,
{メール、名前、パスワード }、
{ models:{userModel }とする。}
):Promise => { を実行します。
コンストラスト user = await userModel.create({
email、
名前
password、
});
return user;
},
},
};
また、トークン・タイプとログイン・クエリによるユーザー・タイプ定義も更新する必要がある。
型トークン
トークン文字列!
}
型 クエリー {
ユーザー(id: ID!):ユーザー!
login(email: String!, password: String!):トークン!
}
では、トークンなしでユーザーを取得してみよう。
OK、うまくいった!ログインしてみる
ヘッダーにトークンを追加して、もう一度ユーザーを取得してみよう。
クールだ!もし間違った認証情報を設定したら?
いいね!
最後のステップ:Netlifyを使ってサーバーレスapiをデプロイする!
フォルダ作成 ラムダ
をメイン・ディレクトリに置き、その中に2つのファイルを置く:
最初にAWSハンドラが含まれています。これはApolloServerLambdaサーバーインスタンスを作成し
そして、そのインスタンスのcreateHandlerを使ってハンドラを公開する。
// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "????";
export const handler = async (
イベントAPIGatewayProxyEvent、
コンテキストコンテキスト
) => {
const server = await createLambdaServer(event, context);
return new Promise((res, rej) => { {: (err: Error, args: any) => (err ? rej(err) ?
const cb = (err: Error, args: any) => (err ? rej(err) : res(args));
server.createHandler()(event, context, cb);
});
};
2つ目はtsconfig。重要なのは アウトディレクトリ
フィールドにいる。
// lambda/tsconfig.json
{
"compilerOptions":{
"sourceMap": true、
"noImplicitAny": true、
"module": "commonjs"、
"target":"es6"、
"experimentalDecorators": true、
「allowSyntheticDefaultImports": true、
"moduleResolution":"ノード",
「skipLibCheck": true、
"esModuleInterop": true、
"outDir":"./dist"
},
"include":["./*.ts", "./**/*.ts", "./**/*.js" ]。
}
しかし、問題がある。私たちは /src
なぜならNetlifyはlambdaフォルダの外にはアクセスできないからだ。そのため、バンドルする必要があります。
npm install --save ncp
ディレクトリをコピーするためのパッケージです。
追加 バンドル
スクリプトを package.json
"スクリプト":{
"bundle":"ncp ./src ./lambda/bundle"、
"codegen":"graphql-codegen --config ./codegen.yml"、
"start":「nodemon"、
},
を実行すると npm run bundle
新しく作成された ラムダ/バンドル
からのすべてのファイルがある。 src/
.
インポートパスの更新 lambda/graphql.ts
// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "./bundle/server";
{...}
ラムダ/バンドルディレクトリを .gitignore
Netlifyにビルドコマンドと関数の場所を教えなければなりません。そのために netlify.toml
file:
// netlify.toml
[ビルド]
コマンド = "npm run build:lambda"
functions = "lambda/dist"
見ての通り、これは アウトディレクトリ
フィールド lambda/tsconfig.json
アプリの構造はこのようになるはずだ(まあ...その一部だが ;)。
アプリ
ラムダ
│ └──バンドル
│ graphql.ts
│ tsconfig.json.ts
└───src
│ └──generated
│ │ graphql.ts
│ index.ts
│ │ model.ts
│ リゾルバ.ts
│ スキーマ.ts
│ サーバー.ts
│
│ codegen.yml
│ graphql.schema.json
│ netlify.toml
│ nodemon.json
│ tsconfig.json
追加 バンドル:ラムダ
スクリプトを package.json
"スクリプト":{
"build:lambda":"npm run bundle && tsc -p lambda/tsconfig.json"、
"bundle":"ncp ./src ./lambda/bundle"、
"codegen":"graphql-codegen --config ./codegen.yml"、
"start":「nodemon"、
},
Netlifyを使ってアプリをデプロイしてみよう。
そしてTAAADAAAA...
これは、エンドポイントがデフォルトでは http://page/
しかし http://page/.netlify/functions/graphql
.
どうすれば直るのか?とても簡単です。ただ リダイレクト
と:
/.netlify/functions/graphql 200!
もう一度デプロイして確認する。
気に入ってもらえただろうか!改良、変更などご自由にどうぞ。
続きを読む
悪いコーディング・プラクティスでプロジェクトを潰さないためには?