├── .babelrc ├── .dockerignore ├── .env ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── index.js ├── package-lock.json ├── package.json └── src ├── model ├── index.js ├── message.js └── user.js ├── resolvers ├── authorization.js ├── index.js ├── message.js └── user.js └── schema ├── index.js ├── message.js └── user.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile* 4 | docker-compose* 5 | .dockerignore 6 | .git 7 | .gitignore 8 | README.md 9 | LICENSE 10 | .vscode 11 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MONGO_URL=mongodb://mongo:27017/mydatabase 2 | ENV=development 3 | PORT=3000 4 | SECRET=qwertyussdfghjkhgfds.2342.dawasdq -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:carbon 2 | # Create app directory 3 | WORKDIR /usr/src/app 4 | # Bundle app source 5 | COPY . . 6 | # npm install 7 | RUN npm install 8 | # Run npm install --global grpc --unsafe-perm 9 | EXPOSE 3000 9204 10 | CMD [ "npm", "run", "start" ] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graphql-fullstack-tutorials 2 | 3 | - Apollo graphql with Mongoose MongoDB 4 | - Apollo Grapqhl with Mysql Sequelize 5 | 6 | https://www.youtube.com/watch?v=d8g2ESwn3XA&list=PLIGDNOJWiL19Nylkg_DJtKpBB-t6tVuRg 7 | 8 | 🚀🚀🚀 React Training Sessions 🚀🚀🚀 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | node: 4 | build: . 5 | ports: 6 | - 3000:3000 7 | volumes: 8 | - ./:/usr/src/app 9 | depends_on: 10 | - mongo 11 | mongo: 12 | image: mongo 13 | container_name: global-mongo-service 14 | restart: unless-stopped 15 | volumes: 16 | - mongo_data:/data/configdb 17 | - mongo_data:/data/db 18 | ports: 19 | - 27017:27017 20 | volumes: 21 | mongo_data: {} -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import cors from 'cors'; 3 | import morgan from 'morgan'; 4 | import http from 'http'; 5 | import jwt from 'jsonwebtoken'; 6 | import DataLoader from 'dataloader'; 7 | import express from 'express'; 8 | import { 9 | ApolloServer, 10 | AuthenticationError, 11 | } from 'apollo-server-express'; 12 | 13 | import schema from './src/schema/index'; 14 | import resolvers from './src/resolvers/index'; 15 | import models, { connectMongo } from './src/model'; 16 | 17 | const app = express(); 18 | 19 | app.use(cors()); 20 | 21 | app.use(morgan('dev')); 22 | 23 | const getMe = async req => { 24 | const token = req.headers['x-token']; 25 | 26 | if (token) { 27 | try { 28 | return await jwt.verify(token, process.env.SECRET); 29 | } catch (e) { 30 | throw new AuthenticationError( 31 | 'Your session expired. Sign in again.', 32 | ); 33 | } 34 | } 35 | }; 36 | 37 | const server = new ApolloServer({ 38 | introspection: true, 39 | typeDefs: schema, 40 | resolvers, 41 | context: async ({ req, connection }) => { 42 | const me = await getMe(req); 43 | return { 44 | models, 45 | me, 46 | secret: process.env.SECRET, 47 | }; 48 | }, 49 | }); 50 | 51 | server.applyMiddleware({ app, path: '/graphql' }); 52 | 53 | const httpServer = http.createServer(app); 54 | server.installSubscriptionHandlers(httpServer); 55 | 56 | const port = process.env.PORT || 3000; 57 | 58 | connectMongo().then(async () => { 59 | 60 | httpServer.listen({ port }, () => { 61 | console.log(`Apollo Server on http://localhost:${port}/graphql`); 62 | }); 63 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullstack-apollo-express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "engines": { 7 | "node": "10.11.0" 8 | }, 9 | "scripts": { 10 | "start": "nodemon --exec babel-node index.js", 11 | "test": "echo \"No test specified\" && exit 0" 12 | }, 13 | "keywords": [], 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/core": "^7.1.6", 17 | "@babel/node": "^7.0.0", 18 | "@babel/preset-env": "^7.1.6", 19 | "@babel/register": "^7.0.0", 20 | "axios": "^0.18.0", 21 | "chai": "^4.2.0", 22 | "mocha": "^5.2.0", 23 | "nodemon": "^1.18.7", 24 | "morgan": "^1.9.1" 25 | }, 26 | "dependencies": { 27 | "apollo-server": "^2.2.3", 28 | "apollo-server-express": "^2.2.3", 29 | "bcrypt": "^3.0.8", 30 | "cors": "^2.8.5", 31 | "dataloader": "^1.4.0", 32 | "dotenv": "^6.1.0", 33 | "express": "^4.16.4", 34 | "graphql": "^14.0.2", 35 | "graphql-iso-date": "^3.6.1", 36 | "graphql-resolvers": "^0.3.2", 37 | "jsonwebtoken": "^8.4.0", 38 | "mongoose": "^5.3.14", 39 | "uuid": "^3.3.2", 40 | "validator": "^10.9.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/model/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose" 2 | import User from './user'; 3 | import Message from './message'; 4 | 5 | 6 | const connectMongo = () =>{ 7 | return mongoose.connect(process.env.MONGO_URL, {useNewUrlParser : true}) 8 | } 9 | 10 | const models = { User, Message }; 11 | 12 | export { connectMongo }; 13 | 14 | export default models; -------------------------------------------------------------------------------- /src/model/message.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const messageSchema = new mongoose.Schema( 4 | { 5 | text: { 6 | type: String, 7 | required: true, 8 | }, 9 | userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 10 | }, 11 | { 12 | timestamps: true, 13 | }, 14 | ); 15 | 16 | const Message = mongoose.model('Message', messageSchema); 17 | 18 | export default Message; 19 | -------------------------------------------------------------------------------- /src/model/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | import bcrypt from 'bcrypt'; 4 | import isEmail from 'validator/lib/isEmail'; 5 | 6 | const userSchema = new mongoose.Schema({ 7 | username: { 8 | type: String, 9 | unique: true, 10 | required: true, 11 | }, 12 | email: { 13 | type: String, 14 | unique: true, 15 | required: true, 16 | validate: [isEmail, 'No valid email address provided.'], 17 | }, 18 | password: { 19 | type: String, 20 | required: true, 21 | minlength: 7, 22 | maxlength: 42, 23 | }, 24 | role: { 25 | type: String, 26 | }, 27 | }); 28 | 29 | userSchema.statics.findByLogin = async function(login) { 30 | let user = await this.findOne({ 31 | username: login, 32 | }); 33 | 34 | if (!user) { 35 | user = await this.findOne({ email: login }); 36 | } 37 | 38 | return user; 39 | }; 40 | 41 | userSchema.pre('remove', function(next) { 42 | this.model('Message').deleteMany({ userId: this._id }, next); 43 | }); 44 | 45 | userSchema.pre('save', async function() { 46 | this.password = await this.generatePasswordHash(); 47 | }); 48 | 49 | userSchema.methods.generatePasswordHash = async function() { 50 | const saltRounds = 10; 51 | return await bcrypt.hash(this.password, saltRounds); 52 | }; 53 | 54 | userSchema.methods.validatePassword = async function(password) { 55 | return await bcrypt.compare(password, this.password); 56 | }; 57 | 58 | const User = mongoose.model('User', userSchema); 59 | 60 | export default User; 61 | -------------------------------------------------------------------------------- /src/resolvers/authorization.js: -------------------------------------------------------------------------------- 1 | import {skip, combineResolvers} from 'graphql-resolvers'; 2 | import { ForbiddenError } from 'apollo-server'; 3 | 4 | 5 | export const isAuthenticated = (parent, args, {me}) => { 6 | if(me){ 7 | return skip; 8 | } 9 | return new ForbiddenError('user is not authenticated'); 10 | } 11 | 12 | export const isAdmin = combineResolvers( 13 | isAuthenticated, 14 | (parent, args , { me : {role}}) => { 15 | return role === 'admin' ? skip : new ForbiddenError('user is not authz for this operation'); 16 | } 17 | ); -------------------------------------------------------------------------------- /src/resolvers/index.js: -------------------------------------------------------------------------------- 1 | import {GraphQLDateTime} from 'graphql-iso-date' 2 | 3 | const customScalarResolver = { 4 | Date: GraphQLDateTime, 5 | }; 6 | 7 | import userResolver from './user'; 8 | 9 | export default [customScalarResolver, userResolver] -------------------------------------------------------------------------------- /src/resolvers/message.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkssharma/Graphql-fullstack-tutorials/215c01ac654f7bb35dbd2dd4617dc3b6acb44367/src/resolvers/message.js -------------------------------------------------------------------------------- /src/resolvers/user.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import { combineResolvers } from 'graphql-resolvers'; 3 | import { AuthenticationError, UserInputError } from 'apollo-server'; 4 | import {combineResolvers} from 'graphql-resolvers' 5 | import { isAdmin, isAuthenticated} from './authorization' 6 | 7 | const createToken = async (user, secret, expiresIn) => { 8 | const { id, email, username, role } = user; 9 | return await jwt.sign({ id, email, username, role }, secret, { 10 | expiresIn, 11 | }); 12 | }; 13 | 14 | export default { 15 | Query: { 16 | users: async (parent, args, { models }) => { 17 | return await models.User.find(); 18 | }, 19 | user: async (parent, { id }, { models }) => { 20 | return await models.User.findById(id); 21 | }, 22 | me: async (parent, args, { models, me }) => { 23 | if (!me) { 24 | return null; 25 | } 26 | return await models.User.findById(me.id); 27 | }, 28 | }, 29 | 30 | Mutation: { 31 | signUp: async ( 32 | parent, 33 | { username, email, password }, 34 | { models, secret }, 35 | ) => { 36 | const user = await models.User.create({ 37 | username, 38 | email, 39 | password, 40 | }); 41 | 42 | return { token: createToken(user, secret, '30m') }; 43 | }, 44 | 45 | signIn: async ( 46 | parent, 47 | { login, password }, 48 | { models, secret }, 49 | ) => { 50 | const user = await models.User.findByLogin(login); 51 | 52 | if (!user) { 53 | throw new UserInputError( 54 | 'No user found with this login credentials.', 55 | ); 56 | } 57 | 58 | const isValid = await user.validatePassword(password); 59 | 60 | if (!isValid) { 61 | throw new AuthenticationError('Invalid password.'); 62 | } 63 | 64 | return { token: createToken(user, secret, '30m') }; 65 | }, 66 | 67 | updateUser: 68 | async (parent, { username }, { models, me }) => { 69 | return await models.User.findByIdAndUpdate( 70 | me.id, 71 | { username }, 72 | { new: true }, 73 | ); 74 | }, 75 | 76 | deleteUser: 77 | async (parent, { id }, { models }) => { 78 | const user = await models.User.findById(id); 79 | 80 | if (user) { 81 | await user.remove(); 82 | return true; 83 | } else { 84 | return false; 85 | } 86 | }, 87 | }, 88 | }; 89 | -------------------------------------------------------------------------------- /src/schema/index.js: -------------------------------------------------------------------------------- 1 | import {gql} from 'apollo-server-express'; 2 | import userSchema from './user'; 3 | 4 | const baseSchema = gql` 5 | scalar Date 6 | 7 | type Query { 8 | _: Boolean 9 | } 10 | 11 | type Mutation { 12 | _: Boolean 13 | } 14 | 15 | type Subscription { 16 | _: Boolean 17 | } 18 | `; 19 | export default [baseSchema, userSchema] -------------------------------------------------------------------------------- /src/schema/message.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkssharma/Graphql-fullstack-tutorials/215c01ac654f7bb35dbd2dd4617dc3b6acb44367/src/schema/message.js -------------------------------------------------------------------------------- /src/schema/user.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server-express'; 2 | 3 | export default gql` 4 | type Token { 5 | token: String! 6 | } 7 | type User { 8 | id: ID! 9 | username: String! 10 | email: String! 11 | role: String 12 | } 13 | extend type Query { 14 | users: [User!] 15 | user(id: ID!): User 16 | me: User 17 | } 18 | extend type Mutation { 19 | signUp( 20 | username: String! 21 | email: String! 22 | password: String! 23 | ): Token! 24 | 25 | signIn(login: String!, password: String!): Token! 26 | updateUser(username: String!): User! 27 | deleteUser(id: ID!): Boolean! 28 | } 29 | ` --------------------------------------------------------------------------------