├── .gitignore ├── nodemon.json ├── schemas ├── index.graphql ├── authors.graphql └── books.graphql ├── resolvers ├── index.js ├── authors.js └── books.js ├── utils └── auth.js ├── README.md ├── package.json ├── data └── index.js └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /etc -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": "js,json,graphql" 3 | } 4 | -------------------------------------------------------------------------------- /schemas/index.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | _empty: String 3 | # define any root queries here 4 | } 5 | 6 | type Mutation { 7 | _empty: String 8 | # define any root queries here 9 | } 10 | -------------------------------------------------------------------------------- /schemas/authors.graphql: -------------------------------------------------------------------------------- 1 | type Author { 2 | id: Int! 3 | name: String! 4 | books: [Book] 5 | } 6 | 7 | extend type Query { 8 | authors: [Author] 9 | author(id: Int!): Author! 10 | } 11 | -------------------------------------------------------------------------------- /resolvers/index.js: -------------------------------------------------------------------------------- 1 | const authorsResolvers = require('./authors'); 2 | const booksResolvers = require('./books'); 3 | 4 | const rootResolver = {}; 5 | 6 | const resolvers = [rootResolver, authorsResolvers, booksResolvers]; 7 | 8 | module.exports = resolvers; 9 | -------------------------------------------------------------------------------- /utils/auth.js: -------------------------------------------------------------------------------- 1 | const authenticateReq = (next) => { 2 | return (root, args, context, info) => { 3 | if (!context.isAuthenticated) { 4 | throw new Error('You are not authorized'); 5 | } 6 | return next(root, args, context, info); 7 | }; 8 | }; 9 | 10 | exports.authenticateReq = authenticateReq; 11 | -------------------------------------------------------------------------------- /schemas/books.graphql: -------------------------------------------------------------------------------- 1 | type Book { 2 | id: Int! 3 | title: String! 4 | authorId: Int! 5 | author: Author! 6 | } 7 | 8 | extend type Query { 9 | books: [Book]! 10 | book(id: Int!): Book! 11 | } 12 | 13 | input NewBook { 14 | title: String! 15 | authorId: Int! 16 | } 17 | 18 | extend type Mutation { 19 | createBook(newBook: NewBook): Book! 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Need help understanding this codebase? [Read this tutorial](https://betterprogramming.pub/architecting-a-graphql-api-codebase-in-node-js-3a951cd7f0f4) 2 | 3 | ## GraphQL Server 4 | 5 | A practical GraphQL Server with Schema and Resolvers Modularization, Authentication, and Resolvers Composition. 6 | 7 | ### Install dependencies 8 | 9 | Run `npm install` from the root to install server dependencies. 10 | 11 | ## Scripts 12 | 13 | ### `npm start` 14 | 15 | Opens the server on PORT 5000 16 | 17 | ### `npm run server` 18 | 19 | Opens the server in dev mode on PORT 5000 20 | -------------------------------------------------------------------------------- /resolvers/authors.js: -------------------------------------------------------------------------------- 1 | const { composeResolvers } = require('@graphql-tools/resolvers-composition'); 2 | const { authors, books } = require('../data'); 3 | const { authenticateReq } = require('../utils/auth'); 4 | 5 | const authorsResolvers = { 6 | Query: { 7 | authors: () => authors, 8 | author: (parent, { id }) => { 9 | return authors.find((author) => author.id === id); 10 | }, 11 | }, 12 | Author: { 13 | books: (author) => { 14 | return books.filter((book) => book.authorId === author.id); 15 | }, 16 | }, 17 | }; 18 | 19 | module.exports = composeResolvers(authorsResolvers, { 20 | '*.*': [authenticateReq], 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-server", 3 | "version": "1.0.0", 4 | "description": "Architecting GraphQL API with NodeJS and Express", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server", 8 | "server": "nodemon server" 9 | }, 10 | "author": "Haseeb Anwar", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@graphql-tools/graphql-file-loader": "^7.1.0", 14 | "@graphql-tools/load": "^7.3.2", 15 | "@graphql-tools/resolvers-composition": "^6.4.0", 16 | "@graphql-tools/schema": "^8.2.0", 17 | "express": "^4.17.1", 18 | "express-graphql": "^0.12.0", 19 | "graphql": "^15.6.0" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^2.0.12" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/index.js: -------------------------------------------------------------------------------- 1 | const authors = [ 2 | { id: 1, name: 'J. K. Rowling' }, 3 | { id: 2, name: 'Winston Groom' }, 4 | { id: 3, name: 'Brent Weeks' }, 5 | ]; 6 | 7 | const books = [ 8 | { id: 1, title: 'Harry Potter and the Chamber of Secrets', authorId: 1 }, 9 | { id: 2, title: 'Harry Potter and the Prisoner of Azkaban', authorId: 1 }, 10 | { id: 3, title: 'Harry Potter and the Goblet of Fire', authorId: 1 }, 11 | { id: 4, title: 'Shrouds of glory', authorId: 2 }, 12 | { id: 5, title: 'Better Times Than These', authorId: 2 }, 13 | { id: 6, title: 'Forrest Gump', authorId: 2 }, 14 | { id: 7, title: 'The Way of Shadows', authorId: 3 }, 15 | { id: 8, title: 'Beyond the Shadows', authorId: 3 }, 16 | ]; 17 | 18 | exports.authors = authors; 19 | exports.books = books; 20 | -------------------------------------------------------------------------------- /resolvers/books.js: -------------------------------------------------------------------------------- 1 | const { composeResolvers } = require('@graphql-tools/resolvers-composition'); 2 | const { authors, books } = require('../data'); 3 | const { authenticateReq } = require('../utils/auth'); 4 | 5 | const booksResolvers = { 6 | Query: { 7 | books: () => books, 8 | book: (parent, { id }) => { 9 | return books.find((book) => book.id === id); 10 | }, 11 | }, 12 | Book: { 13 | author: (book) => { 14 | return authors.find((author) => author.id === book.authorId); 15 | }, 16 | }, 17 | Mutation: { 18 | createBook: (parent, { newBook }) => { 19 | const createdBook = { id: books.length + 1, ...newBook }; 20 | books.push(createdBook); 21 | return createdBook; 22 | }, 23 | }, 24 | }; 25 | 26 | module.exports = composeResolvers(booksResolvers, { 27 | '*.*': [authenticateReq], 28 | }); 29 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { graphqlHTTP } = require('express-graphql'); 3 | const { makeExecutableSchema } = require('@graphql-tools/schema'); 4 | const { loadSchemaSync } = require('@graphql-tools/load'); 5 | const { GraphQLFileLoader } = require('@graphql-tools/graphql-file-loader'); 6 | const graphqlResolver = require('./resolvers'); 7 | 8 | const server = express(); 9 | const PORT = process.env.PORT || 5000; 10 | 11 | const schema = makeExecutableSchema({ 12 | typeDefs: loadSchemaSync('schemas/**/*.graphql', { 13 | loaders: [new GraphQLFileLoader()], 14 | }), 15 | resolvers: graphqlResolver, 16 | }); 17 | 18 | server.use((req, res, next) => { 19 | // extract token from req headers 20 | const token = req.header('Authorization') || 'fake'; 21 | 22 | // TODO: verify token 23 | 24 | // TODO: bind user to req 25 | 26 | // we can later access isAuthenticated property 27 | // in resolver functions to check 28 | // if the user is authenticated 29 | req.isAuthenticated = Boolean(token); 30 | 31 | // call the next middleware 32 | // whether the user is authenticated or not 33 | next(); 34 | }); 35 | 36 | server.use( 37 | '/graphql', 38 | graphqlHTTP({ 39 | schema, 40 | graphiql: true, 41 | }) 42 | ); 43 | 44 | server.listen(PORT, () => console.log(`Listening on PORT ${PORT}`)); 45 | --------------------------------------------------------------------------------