Closed spyshower closed 5 years ago
Have you read this article (you may need to read this in Incognito Mode)? https://medium.freecodecamp.org/build-an-apollo-graphql-server-with-typescript-and-webpack-hot-module-replacement-hmr-3c339d05184f
This worked for me nicely, but you need to have your Webpack process running in one terminal tab and executing the JS code in a secondary terminal tab.
@DevNebulae I managed to get it running via another way!
@spyshower For documentation's sake, could you please post your resolution? Don't be like DenverCoder9.
Deleted conjecture.
resolvers.js
const resolvers = {
Subscription: {
...
},
Query: {
..
}
}
export default resolvers
typeDefs.js
const typeDefs = gql`
type Subscription {
addUser(name: String!): ...
}
export default typeDefs
index.js
import http from 'http';
import express from 'express'
const requestIp = require('request-ip');
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
const typeDefs = require('./typeDefs').default
const resolvers = require('./resolvers').default
import getUserByToken from './getUserByToken';
const app = express();
const PORT = 8081;
app.use(requestIp.mw())
const server = new ApolloServer({ schema,
subscriptions: {
onConnect: (connectionParams, webSocket) => {
console.log("onConnect server")
if (connectionParams.authToken) {
return getUserByToken(connectionParams.authToken, "user")
.then(user => {
// console.log('then -> ' + user.id)
return {
currentUser: user.dataValues,
};
})
.catch(error => {
console.log('error index.js server onConnect auth: ' + error)
});
}
throw new Error('Missing auth token!');
}
},
context: ({ req }) => {
// const ip = req.clientIp;
// console.log('ip: ' + ip)
req.headers.ip = req.clientIp
return req.headers
},
});
// THE MAGIC HAPPENS HERE <3
app.use('/graphql', (req, res, next) => {
const typeDefs = require('./typeDefs').default
const resolvers = require('./resolvers').default
const schema = makeExecutableSchema({ typeDefs, resolvers })
// console.log(schema)
server.schema = schema
next();
})
server.applyMiddleware({ app });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`)
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`)
})
let currentApp = app
if (module.hot) {
// add whatever file you wanna watch
module.hot.accept(['./index', './typeDefs', './resolvers'], () => {
httpServer.removeListener('request', currentApp);
httpServer.on('request', app);
currentApp = app;
});
}
The typeDefs/resolvers files are incomplete of course.
I was searching the same problem and arrived here. After some minutes of researching, reading code, I came up with a cleaner solution:
import http from 'http';
import express from 'express';
import console from 'chalk-console';
import { ApolloServer } from 'apollo-server-express';
import { LocalStorage } from 'node-localstorage';
import { PubSub } from 'apollo-server';
const localStorage = new LocalStorage('./data');
const pubsub = new PubSub();
const typeDefs = require('./schemas').default;
const resolvers = require('./resolvers').default(localStorage, pubsub);
const PORT = process.env.PORT || 8081;
const configureHttpServer = (httpServer) => {
console.info('Creating Express app');
const expressApp = express();
console.info('Creating Apollo server');
const apolloServer = new ApolloServer({
typeDefs,
resolvers
});
apolloServer.applyMiddleware({
app: expressApp
});
console.info('Express app created with Apollo middleware');
httpServer.on('request', expressApp);
apolloServer.installSubscriptionHandlers(httpServer);
}
if(!process.httpServer)
{
console.info('Creating HTTP server');
process.httpServer = http.createServer();
configureHttpServer(process.httpServer);
process.httpServer.listen(PORT, () => {
console.info(`HTTP server ready at http://localhost:${PORT}`);
console.info(`Websocket server ready at ws://localhost:${PORT}`);
});
} else {
console.info('Reloading HTTP server');
process.httpServer.removeAllListeners('upgrade');
process.httpServer.removeAllListeners('request');
configureHttpServer(process.httpServer);
console.info('HTTP server reloaded');
}
if (module.hot) {
module.hot.accept();
}
Init a new Express & Apollo & WS every time
Are you sure about that? Seems overkill
Init a new Express & Apollo & WS every time
Are you sure about that? Seems overkill
I think it should be better than calling makeExecutableSchema every request. Here I just do it every time we reload.
@MartinPham Gonna try that, since my approach doesn't work for apollo-server
>=2.6.6
@MartinPham Gonna try that, since my approach doesn't work for
apollo-server
>=2.6.6
Let me know :) I was using the latest version btw
@spyshower I've used here https://github.com/MartinPham/graphql-todo/blob/master/graphql/src/index.js
Thanks @MartinPham ! I used your solution but instead of setting the httpServer on the process, I just reloaded if the file where I have the schemas changed.
const httpServer = http.createServer()
configureHttpServer(httpServer)
httpServer.listen({ port: PORT }, () => console.log(`🚀 Server ready`))
if (module.hot) {
module.hot.accept(['./schemas'], () => {
console.info('Reloading HTTP server');
httpServer.removeAllListeners('request')
configureHttpServer(httpServer)
})
}
Thanks @MartinPham ! I used your solution but instead of setting the httpServer on the process, I just reloaded if the file where I have the schemas changed.
const httpServer = http.createServer() configureHttpServer(httpServer) httpServer.listen({ port: PORT }, () => console.log(`🚀 Server ready`)) if (module.hot) { module.hot.accept(['./schemas'], () => { console.info('Reloading HTTP server'); httpServer.removeAllListeners('request') configureHttpServer(httpServer) }) }
but probably you'd need to reload the resolvers also?
Thanks @MartinPham ! I used your solution but instead of setting the httpServer on the process, I just reloaded if the file where I have the schemas changed.
const httpServer = http.createServer() configureHttpServer(httpServer) httpServer.listen({ port: PORT }, () => console.log(`🚀 Server ready`)) if (module.hot) { module.hot.accept(['./schemas'], () => { console.info('Reloading HTTP server'); httpServer.removeAllListeners('request') configureHttpServer(httpServer) }) }
but probably you'd need to reload the resolvers also?
Yea, sorry, I didn't put all the code in the response but this is what our configureHttpServer
looks like:
const configureHttpServer = async (httpServer: http.Server): Promise<void> => {
const app = express()
const schema = await allSchemas()
const server = new ApolloServer({
schema
} as Config)
server.applyMiddleware({ app, path: '/' })
httpServer.on('request', app)
}
We use schema stitching and the allSchemas() function returns a graphQL schema.
Can you please help? @MartinPham Getting Following:
[HMR] - ./schema.ts
[HMR] - ./express.ts
[HMR] Update applied.
events.js:187
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use :::8081
Hi not sure if you are still having issues, but this would of helped me sooner if it was answered. Make sure to do a server.stop())
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => server.stop());
}
Can you please help? @MartinPham Getting Following:
[HMR] - ./schema.ts [HMR] - ./express.ts [HMR] Update applied. events.js:187 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE: address already in use :::8081
@MartinPham,
I am a beginning Dev, but advanced sysadmin. I'm using this setup to learn about GQL.
I have the app up and running in webpack, But every reload fails on:
[22:06:22] [INFO] �🧦 Websocket server ready at ws://localhost:3000/graphq
internal/process/per_thread.js:180
throw new ERR_UNKNOWN_SIGNAL(sig);
^
TypeError [ERR_UNKNOWN_SIGNAL]: Unknown signal: SIGUSR2
at StartServerPlugin.afterEmit (D:\1. Repos\tif-graphql-api-server\node_modules\start-server-webpack-plugin\dist\StartServerPlugin.js:85:17)
at AsyncSeriesHook.eval [as callAsync] (eval at create (D:\1. Repos\tif-graphql-api-server\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:7:1)
at asyncLib.forEachLimit.err (D:\1. Repos\tif-graphql-api-server\node_modules\webpack\lib\Compiler.js:482:27)
at D:\1. Repos\tif-graphql-api-server\node_modules\neo-async\async.js:2818:7
at done (D:\1. Repos\tif-graphql-api-server\node_modules\neo-async\async.js:3522:9)
at AsyncSeriesHook.eval [as callAsync] (eval at create (D:\1. Repos\tif-graphql-api-server\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:6:1)
at outputFileSystem.writeFile.err (D:\1. Repos\tif-graphql-api-server\node_modules\webpack\lib\Compiler.js:464:33)
at D:\1. Repos\tif-graphql-api-server\node_modules\graceful-fs\graceful-fs.js:57:14
at FSReqCallback.args [as oncomplete] (fs.js:145:20)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
My Index.js
require('dotenv').config()
import http from 'http'
import express from 'express'
import console from 'chalk-console'
import { gql, ApolloServer } from 'apollo-server-express'
const books = require('./domains/books')
const magazines = require('./domains/magazines')
import { LocalStorage } from 'node-localstorage'
import { PubSub } from 'apollo-server-express'
const localStorage = new LocalStorage('./data')
const pubsub = new PubSub()
//const typeDefs = require('./schemas').default
//const resolvers = require('./resolvers').default(localStorage, pubsub)
const typeDef = gql`
type Query
`
var PORT = process.env.PORT || 4000
const configureHttpServer = (httpServer) => {
console.info('Creating Express app')
const expressApp = express()
console.info('Creating Apollo server')
const apolloServer = new ApolloServer({
typeDefs: [typeDef, books.typeDef, magazines.typeDef],
resolvers: [books.resolvers, magazines.resolvers],
playground: process.env.NODE_ENV !== 'production'
})
apolloServer.applyMiddleware({
app: expressApp
})
console.info('Express app created with Apollo middleware')
httpServer.on('request', expressApp)
apolloServer.installSubscriptionHandlers(httpServer)
}
if (!process.httpServer) {
console.info('Creating HTTP server')
process.httpServer = http.createServer()
configureHttpServer(process.httpServer)
process.httpServer.listen(PORT, () => {
console.info(` 🚀 HTTP server ready at http://localhost:${PORT}/graphql !`)
console.info(` 🧦 Websocket server ready at ws://localhost:${PORT}/graphql !`)
})
} else {
console.info('⌛ Reloading HTTP server')
process.httpServer.removeAllListeners('upgrade')
process.httpServer.removeAllListeners('request')
configureHttpServer(process.httpServer)
console.info('👍 HTTP server reloaded')
}
if (module.hot) {
module.hot.accept()
module.hot.dispose(() => server.stop())
}
My package.json:
{
"name": "tif-graphql-api-server",
"version": "0.0.1",
"description": "GRaphQL server for This Is Fashion apps",
"main": "index.js",
"repository": "https://gitlab.com/thisisfashion/tif-graphql-api-server.git",
"author": "Sander Kooger <sander@thisisfashion.tv>",
"license": "UNLICENCE",
"private": true,
"scripts": {
"build": "webpack --config webpack.production.js",
"dev": "webpack --config webpack.development.js",
"start": "node -r esm src",
"start:webpack": "node dist/server.js",
"lint": "eslint \"./**/*.{js,jsx,json}\" ",
"prettier": "prettier --write \"./**/*.{js,jsx,json,css}\""
},
"husky": {
"hooks": {
"pre-commit": "cross-env lint-staged",
"pre-push": "cross-env lint-staged"
}
},
"lint-staged": {
"*.{css,js,jsx,json}": [
"prettier --write"
]
},
"engines": {
"node": ">=6"
},
"dependencies": {
"cross-env": "^7.0.2",
"dotenv": "^8.2.0",
"apollo-server-express": "^2.12.0",
"body-parser": "^1.19.0",
"chalk-console": "^1.1.0",
"cross-env": "^7.0.2",
"dotenv": "^8.2.0",
"esm": "^3.2.25",
"express": "^4.17.1",
"graphql": "^15.0.0",
"graphql-tools": "^5.0.0",
"http": "^0.0.1-security",
"node-localstorage": "^2.1.6"
},
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"husky": "^4.2.5",
"lint-staged": "^10.1.3",
"prettier": "^2.0.5",
"start-server-webpack-plugin": "^2.2.5",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-merge": "^4.2.2",
"webpack-node-externals": "^1.7.2"
}
}
I was building this boilerplate for internal user in an application later. But i have an idea. I think a lot of people are looking for an apollo-server that does:
HMR Domain based schema building (EG a ./src/domain directory that contains different folders per datatype you need resolvers etc for) getUserByToken (Have data that the server spits out without/ and data only visible for authenticated users. ) @spyshower thank you for the inspiration.
Maybe its a good idea to build something like this, Kind of like NextJs for GQL? I am more than willing to contribute, and use it in production / Help out with documentation.
I'm even willing to be the Idiot, so we can make it idiot-proof ;)
On Webpack, I see this:
But nothing is updated. I have tried some other solutions I found online regarding webpack's output.publicPath. I have no idea what else to do and soon I am going to production. Restarting the server is not an option for me.
My code:
I checked out https://github.com/apollographql/apollo-server/issues/1275 but found no solution.