Markmið Upphafleg stilling Settu upp háð kerfi Byrjum Fyrst skaltu bæta tsconfig.json í aðalmöppuna: Nú skulum við búa til src/server.ts fyrir netþjónauppsetningar. Bættu síðan við tveimur fallum: eitt fyrir staðbundinn netþjón og hitt fyrir lambda. OK, við höfum engin resolvers né gerðarskilgreiningar, svo við þurfum að búa til nokkur. Gerum ráð fyrir að við viljum í fyrstu […]
Markmið
Stilla bæði staðbundna og lambda-þjóna.
Tengdu bæði við MongoDB.
Innleiða grunnauðkenningu.
Setja upp serverlausan Apollo GraphQL forritaskil með Netlify.
Notaðu Typescript .
Upphafleg stilling
npm init -yHljóðskrift
Setja upp forsendur
npm install --save týpskrift graphql aws-lambda @types/aws-lambdaHljóðskrift
Skulum byrja
Fyrst, bættu við the tsconfig.json í aðalmöppu:
"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"]
}Hljóðskrift
Nú skulum við búa til uppspretta/þjónn.ts fyrir netþjónauppsetningar. Bættu síðan við tveimur fallum: eitt fyrir staðbundinn netþjón og hitt fyrir lambda.
// 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 };
OK, við höfum engar lausnir né gerðarskilgreiningar, svo við þurfum að búa til nokkrar. Segjum að við viljum fyrst búa til notendur og fá upplýsingar um þá.
// src/schemas.ts
const { gql } = require("apollo-server-lambda");
const userSchema = gql`
type User {
id: ID!
email: String!
name: String!
}
type Mutation {
createUser(name: String!, email: String!, password: String!): User!
}
`;
Ef þú ert ekki kunnugur þessu, Apollo undirbjó mjög góða kennsluleiðbeiningu.
Nú skulum við búa til notendauppleysara með einni fyrirspurn og einni mutasjón.
// src/resolvers.ts
const userResolver = {
Query: {
user: async (parent, args, context, info) => {
{...}
},
},
};
Mutation: {
createUser: async (parent, args, context, info) => {
{...}
},
},
};
En við höfum engin gögn… Látum laga það 😉
Ekki gleyma að flytja inn gerðarskilgreiningu og lausnara á netþjóninn.
// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";
import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers";
{...}
Tengjast MongoDB með mongoose
Nú er góður tími til að tengjast gagnagrunninum okkar. Í þessu tilviki verður það MongoDB. Það er ókeypis og auðvelt í viðhaldi. En áður en við byrjum skulum við setja upp tvær fleiri forsendur:
npm install --save mongoose dotenv
Fyrsta skrefið er að búa til notendalíkan.
// src/model.ts
import mongoose, { Document, Error, Schema } from "mongoose";
export type User = Document & {
_id: string,
email: string,
name: string,
password: string,
};
delete mongoose.connection.models["User"];
const UserSchema: Schema = new Schema({
email: {
type: String,
required: true,
unique: true,
},
name: {
type: String,
required: true,
minLength: 3,
maxLength: 32,
},
password: {
type: String,
required: true,
},
});
export const userModel = mongoose.model ("User", UserSchema);
Gerið lykilorð öruggari
Öryggi fyrst! Nú skulum við tryggja öryggi lykilorða okkar með því að haska þau.
npm install --save bcrypt @types/bcryptHljóðskrift
Nú skaltu innleiða lykilorðsvörnina innan pre-middleware callback-sins. Pre-middleware-aðgerðir eru keyrðar hver á eftir annarri þegar hver middleware kallar next. Til að gera lykilorðin örugg notum við aðferð sem býr til salt og skrá á aðskildum fallaköllunum.
// src/model.ts
import bcrypt from "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) {
if (err) return next(err);
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
user.password = hash;
next();
});
});
});
{...}
Bættu síðan comparePasswords-aðferð við UserSchema:
// 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) => {
if (err) {
return cb(err, null);
}
cb(null, isMatch);
});
};
{...}
Og auðvitað breyta notendagerð.
type comparePasswordFunction = (
candidatePassword: string,
cb: (err: Error, isMatch: boolean) => void)
=> void;
export type User = Document & {
_id: string,
email: string,
name: string,
password: string,
comparePasswords: comparePasswordFunction,
};
Nú getum við komið á tengingu milli netþjóna og gagnagrunnanna. MONGODB_URI er umhverfisbreyta sem inniheldur tengistreng sem þarf til að stofna tenginguna. Þú getur fengið hana í klústerspjaldinu þínu eftir að þú skráir þig inn í MongoDB Atlas-reikninginn þinn. Settu hana inn í .umhverfi
// .env
MONGODB_URI = ...;
Mundu alltaf að bæta þeirri skrá við .gitignore. Frábært! Nú skulum við bæta við falli sem gerir okkur kleift að tengjast gagnagrunninum.
// src/server.ts
import mongoose, { Connection } from "mongoose";
{...}
let cachedDb: Connection;
const connectToDatabase = async () => {
if (cachedDb) return;
await mongoose.connect(process.env.MONGODB_URI || "", {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true,
});
cachedDb = mongoose.connection;
};
{...}
Samhengi er hlutur sem er sameiginlegur öllum lausnara. Til að útvega það þurfum við bara að bæta við upphafsstillingarfalli fyrir samhengi í smiðinn á ApolloServer. Skulum gera það.
// src/server.ts
import { userModel } from "./models/user.model";
{...}
const createLambdaServer = async () =>
new ApolloServerLambda({
typeDefs,
resolvers,
introspection: true,
playground: true,
context: async () => {
await connectToDatabase();
return {
models: {
userModel,
},
};
},
});
const createLocalServer = () =>
new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: true,
context: async () => {
await connectToDatabase();
return {
models: {
userModel,
},
};
}
});
Eins og þú sérð, erum við líka að ljúka. notendalíkan í gegnum samhengi. Við getum nú uppfært resolverinn:
// resolvers.ts
const userResolver = {
Query: {
user: async (_, { email, name, password }, { models: { userModel } }) => {
const user = await userModel.findById({ _id: id }).exec();
return user;
},
},
};
Lítur vel út! Búðu nú til einfalt netþjónsdæmi:
// src/index.ts
import { createLocalServer } from "./server";
require("dotenv").config();
const port = process.env.PORT || 4000;
const server = createLocalServer();
server.listen(port).then(({ url }) => {
console.log(`Server ir running at ${url}`);
});
Ræsa staðbundinn netþjón með Nodemon
Síðasta hlutinn áður en hlaupið hefst, bætið við nodemon.json
{
"watch": ["src"],
"ext": ".ts,.js",
"ignore": [],
"exec": "ts-node --transpile-only ./src/index.ts"
}
Bættu skriftu við package.json
"scripts": {
"start": "nodemon"
},
Og hlaupa!
Hafna npm.
Þú ættir að fá slíkar upplýsingar í terminalnum:
Þjónninn er keyrður á http://localhost:4000/
Ef já, opnaðu http://localhost:4000/ innan í vafranum þínum.
GraphQL-leikvöllurinn ætti að birtast. Sköpum nýjan notanda!
Skoðaðu hvernig það lítur út í gagnagrunninum.
Frábært! Allt virkar vel!
Reynum að fá nokkrar notendaupplýsingar.
Og bættu við lykilorðareitnum…
Frábært! Við fáum villu Ekki er hægt að fyrirspyrja reitinn "password" á gerðinni "User".". Eins og þú getur séð aftur, bættum við þessu reit ekki inn í skilgreiningu notendategundarinnar. Hann er þar viljandi. Við ættum ekki að gera kleift að fyrirspyrja neitt lykilorð eða aðrar viðkvæmar gögn .
Annað… Við getum fengið notendagögn án nokkurrar auðkenningar… þetta er ekki góð lausn. Við verðum að laga þetta.
En áður en…
Stilla Codegen
Skoðum GraphQL kóði rafall til að fá samhæfa grunntegund, byggða á skema okkar.
npm install --save @graphql-codegen/cli @graphql-codegen/introspection
@graphql-codegen/typescript @graphql-codegen/typescript-resolvers
Búa til kóðagerð.yml
yfirskrift: satt
skema: "http://localhost:4000"
framleiðir:
./src/generated/graphql.ts:
viðbætur:
- "typescript"
- "typescript-resolvers"
./graphql.schema.json:
viðbætur:
- "introspection"
Bæta við kóðagerð handrit til package.json
"scripts": {
"start": "nodemon",
"codegen": "graphql-codegen --config ./codegen.yml",
},
Þá, þegar staðbundni netþjónninn er í gangi, keyrið handritið:
npm run codegen
Ef vel tekst til færðu skilaboðin:
√ Greina stillingu
√ Búa til úttak
Ef þú færð þessar upplýsingar, ættu tvær skrár að birtast:
graphql.schema.json í aðalmappunni
graphql.ts í nýlega búinni leið uppruna/framleitt
Við höfum meiri áhuga á þeim seinni. Ef þú opnar hann, munt þú taka eftir fallegum gerðauppbyggingu.
Nú getum við bætt leysara okkar:
// src/resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";
const userResolver: Resolvers = {
Query: {
user: async (_, { id }, { models: { userModel }, auth }): Promise => {
const user = await userModel.findById({ _id: id }).exec();
return user;
},
},
Mutation: {
createUser: async (
_,
{ email, name, password },
{ models: { userModel } }
): Promise => {
const user = await userModel.create({
email,
name,
password,
});
return user;
},
},
};
Auðkenning
Næst skulum við setja upp einfalda auðkenningu byggða á táknum.
npm install --save jsonwebtoken @types/jsonwebtoken
Búa til Athuga auðkenningu Fall til að staðfesta hvort tokenið sé gilt. Við munum bæta niðurstöðunni við samhengi til að við getum nálgast hana innan lausnara.
// 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,
},
};
},
});
}
Einnig þurfum við leið til að búa til slíkt token. Besta leiðin er að innleiða innskráningarfyrirspurn í lausnara okkar.
// resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";
const userResolver: Resolvers = {
Query: {
user: async (_, { id }, { models: { userModel }, auth }): Promise => {
if (!auth) throw new AuthenticationError("Þú ert ekki staðfestur");
const user = await userModel.findById({ _id: id }).exec();
return user;
},
login: async (
_,
{ 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 };
},
},
Mutation: {
createUser: async (
_,
{ email, name, password },
{ models: { userModel } }
): Promise => {
const user = await userModel.create({
email,
name,
password,
});
return user;
},
},
};
Þú þarft einnig að uppfæra skilgreiningar á notendategundum eftir token-gerð og innskráningar-fyrirspurn.
type Token {
token: String!
}
type Query {
user(id: ID!): User!
login(email: String!, password: String!): Token!
}
Reynum nú að fá notanda án token
OK, virkar vel! Reyndu að skrá þig inn.
Reynum aftur að fá notandann, en að þessu sinni með token bætt við hausana.
Frábært! Og hvað ef við sláum inn rangar auðkenningargögn?
Fínt!
Undirbúa uppsetningu
Síðasta skref: Deildu þjónustulausu API-inu með Netlify!
Búa til möppu lamda Í aðalmappunni og settu tvær skrár inn í hana:
Fyrst inniheldur AWS handler. Hann býr til ApolloServerLambda-þjónsinstans og Þá skaltu búa til meðhöndlara með createHandler á þeirri instans.
// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "???";
export const handler = async (
event: APIGatewayProxyEvent,
context: Context)
=> {
const server = await createLambdaServer(event, context);
return new Promise((res, rej) => {
const cb = (err: Error, args: any) => (err ? rej(err) : res(args));
server.createHandler()(event, context, cb);
});
};
Þú getur lesið meira um það.
Annar er tsconfig. Mikilvægur hluti er útgáfuskrá svið.
// lambda/tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "hnútur ",
"skipLibCheck": true,
"esModuleInterop": true,
"outDir": "./dist"
},
"include": ["./*.ts", "./**/*.ts", "./**/*.js"]
}
En það er vandamál. Við getum ekki notað /uppspretta dir vegna þess að Netlify nær ekki utan um lambda-möppuna. Þess vegna verðum við að pakka henni.
npm install --save ncp
Þetta er pakki sem gerir okkur kleift að afrita möppu.
Bæta við pakki handrit til package.json
"scripts": {
"bundle": "ncp ./src ./lambda/bundle",
"codegen": "graphql-codegen --config ./codegen.yml",
"start": "nodemon",
},
Nú ef þú hleypur npm run bundle, þú sérð, að í nýlega búin lambda/bundle dir við höfum allar skrár frá uppspretta/.
Uppfærðu innflutningsslóðina innan lambda/graphql.ts
// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "./bundle/server";
{...}
Þú getur nú bætt lambda/bundle dir við .gitignore
Setja upp með Netlify
Við verðum að segja Netlify hvaða byggingarskipun er og hvar fallin okkar eru. Til þess skulum við búa til netlify.toml file:
// netlify.toml
[build]
command = "npm run build:lambda"
functions = "lambda/dist"
Eins og þú sérð, er þetta sama möppan sem skilgreind er sem a útgáfuskrá Slá inn lambda/tsconfig.json
Svona ætti uppbygging forritsins þíns að líta út (jæja… hluti af því ;))
app
└───lambda
│ └────bundle
│ │ graphql.ts
│ tsconfig.json.ts
└───src
│ └───generated
│ │ │ graphql.ts
│ │ index.ts
│ │ model.ts
│ │ resolvers.ts
│ │ schemas.ts
│ server.ts
│
│ codegen.yml
│ graphql.schema.json
│ netlify.toml
│ nodemon.json
│ tsconfig.json
Bæta við pakki:lambda handrit til package.json
"scripts": {
"build:lambda": "npm run bundle && tsc -p lambda/tsconfig.json",
"bundle": "ncp ./src ./lambda/bundle",
"codegen": "graphql-codegen --config ./codegen.yml",
"start": "nodemon",
},
Setja upp
Reynum að setja forritið okkar upp á Netlify.
Skráðu þig inn á Netlify-reikninginn þinn,
Tengstu við GitHub-reikninginn þinn,
Smelltu á hnappinn ‘Ný vefsíða úr Git’,
Veldu viðeigandi geymslu.,
Settu upp byggingar-skipunina npm run build:lambda,
Bættu við umhverfisbreytu (MONGODB_URI),
Setja upp…
Og tadaaa…
Það er vegna þess að endapunkturinn er sjálfgefið ekki http://page/ en http://page/.netlify/functions/graphql.
Hvernig á að laga það? Það er mjög einfalt. Bara búa til _redirects með:
/ /.netlify/functions/graphql 200!
Setjið aftur upp og athugið.
Vonandi líkaði þér við það! Endilega endurbættu og breyttu því.
Lesa meira:
Hvernig á ekki að drepa verkefni með slæmum forritunarvenjum?
Öryggi vefumsókna. Veikleiki Target=”_blank”
Öryggi vefumsókna – XSS-veikleiki