set up Apollo server!

This commit is contained in:
Matt Dunn-Rankin 2020-04-22 11:51:36 -07:00
parent 513b2c12e7
commit ecebb93ec5
8 changed files with 5547 additions and 0 deletions

119
server/.gitignore vendored Normal file
View file

@ -0,0 +1,119 @@
.env
# From https://github.com/github/gitignore/blob/2a4de265d37eca626309d8e115218d18985b5435/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*

15
server/db.js Normal file
View file

@ -0,0 +1,15 @@
require("dotenv").config();
const mysql = require("mysql2/promise");
async function connectToDb() {
const db = await mysql.createConnection({
host: "impress.openneo.net",
user: process.env["IMPRESS_MYSQL_USER"],
password: process.env["IMPRESS_MYSQL_PASSWORD"],
database: "openneo_impress",
});
return db;
}
module.exports = connectToDb;

44
server/index.js Normal file
View file

@ -0,0 +1,44 @@
const { ApolloServer, gql } = require("apollo-server");
const connectToDb = require("./db");
const { loadItems, loadItemTranslation } = require("./loaders");
const typeDefs = gql`
type Item {
id: ID!
name: String!
}
type Query {
items(ids: [ID!]!): [Item!]!
}
`;
const resolvers = {
Item: {
name: async (item, _, { db }) => {
const translation = await loadItemTranslation(db, item.id, "en");
return translation.name;
},
},
Query: {
items: (_, { ids }, { db }) => loadItems(db, ids),
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: async () => {
const db = await connectToDb();
return { db };
},
});
if (require.main === module) {
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
}
module.exports = { server };

97
server/index.test.js Normal file
View file

@ -0,0 +1,97 @@
const gql = require("graphql-tag");
const { createTestClient } = require("apollo-server-testing");
const connectToDb = require("./db");
const actualConnectToDb = jest.requireActual("./db");
const { server } = require("./index");
const { query } = createTestClient(server);
// Spy on db.execute, so we can snapshot the queries we run. This can help us
// keep an eye on perf - watch for tests with way too many queries!
jest.mock("./db");
let queryFn;
beforeEach(() => {
numQueries = 0;
connectToDb.mockImplementation(async (...args) => {
const db = await actualConnectToDb(...args);
queryFn = jest.spyOn(db, "execute");
return db;
});
});
afterEach(() => {
jest.resetAllMocks();
});
it("can load items", async () => {
const res = await query({
query: gql`
query($ids: [ID!]!) {
items(ids: $ids) {
id
name
}
}
`,
variables: {
ids: [
38913, // Zafara Agent Gloves
38911, // Zafara Agent Hood
38912, // Zafara Agent Robe
],
},
});
expect(res.errors).toBeFalsy();
expect(res.data).toMatchInlineSnapshot(`
Object {
"items": Array [
Object {
"id": "38911",
"name": "Zafara Agent Hood",
},
Object {
"id": "38912",
"name": "Zafara Agent Robe",
},
Object {
"id": "38913",
"name": "Zafara Agent Gloves",
},
],
}
`);
expect(queryFn.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"SELECT * FROM items WHERE id IN (?,?,?)",
Array [
"38913",
"38911",
"38912",
],
],
Array [
"SELECT * FROM item_translations WHERE item_id = ? AND locale = ? LIMIT 1",
Array [
38911,
"en",
],
],
Array [
"SELECT * FROM item_translations WHERE item_id = ? AND locale = ? LIMIT 1",
Array [
38912,
"en",
],
],
Array [
"SELECT * FROM item_translations WHERE item_id = ? AND locale = ? LIMIT 1",
Array [
38913,
"en",
],
],
]
`);
});

26
server/loaders.js Normal file
View file

@ -0,0 +1,26 @@
async function loadItems(db, ids) {
const qs = ids.map((_) => "?").join(",");
const [rows, _] = await db.execute(
`SELECT * FROM items WHERE id IN (${qs})`,
ids
);
return rows;
}
async function loadItemTranslation(db, itemId, locale) {
const [
rows,
_,
] = await db.execute(
`SELECT * FROM item_translations WHERE item_id = ? AND locale = ? LIMIT 1`,
[itemId, locale]
);
if (rows.length === 0) {
throw new Error(`could not load translation for ${itemId}, ${locale}`);
}
return rows[0];
}
module.exports = { loadItems, loadItemTranslation };

25
server/package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "impress-2020-server",
"version": "1.0.0",
"main": "index.js",
"author": "Matchu",
"license": "MIT",
"dependencies": {
"apollo-server": "^2.12.0",
"dotenv": "^8.2.0",
"graphql": "^15.0.0",
"mysql2": "^2.1.0"
},
"devDependencies": {
"apollo-server-testing": "^2.12.0",
"jest": "^25.4.0",
"nodemon": "^2.0.3",
"prettier": "^2.0.5"
},
"scripts": {
"start": "node index.js",
"watch": "nodemon index.js",
"test": "jest",
"setup-mysql-user": "mysql -h impress.openneo.net -u matchu -p < setup-mysql-user.sql"
}
}

View file

@ -0,0 +1,2 @@
GRANT SELECT ON openneo_impress.items TO impress2020;
GRANT SELECT ON openneo_impress.item_translations TO impress2020;

5219
server/yarn.lock Normal file

File diff suppressed because it is too large Load diff