Repository: garganurag893/Next.js_GraphQL_Express_Apollo_Boilerplate Branch: master Commit: 54cfdec9469f Files: 89 Total size: 95.2 KB Directory structure: gitextract_51xeknsa/ ├── .github/ │ ├── FUNDING.yml │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ └── feature_request.md ├── Express_GraphQL_Apollo_Mongodb_Server/ │ ├── .gitignore │ ├── config/ │ │ ├── express.ts │ │ └── index.ts │ ├── index.ts │ ├── package.json │ ├── server/ │ │ ├── graphql/ │ │ │ ├── resolvers/ │ │ │ │ ├── index.ts │ │ │ │ ├── merge.ts │ │ │ │ └── user.ts │ │ │ └── schema/ │ │ │ └── index.ts │ │ ├── helpers/ │ │ │ └── date.ts │ │ ├── middleware/ │ │ │ └── auth.ts │ │ └── models/ │ │ └── user.ts │ ├── tsconfig.json │ └── tslint.json ├── LICENSE ├── README.md ├── nextjs-app/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── next-env.d.ts │ ├── package.json │ ├── pages/ │ │ ├── _app.tsx │ │ ├── index.tsx │ │ ├── signup.tsx │ │ ├── subscription.tsx │ │ ├── update.tsx │ │ ├── users.tsx │ │ └── welcome.tsx │ ├── src/ │ │ ├── components/ │ │ │ ├── Card.tsx │ │ │ ├── Footer.tsx │ │ │ ├── List.tsx │ │ │ └── Login.tsx │ │ ├── config/ │ │ │ └── index.ts │ │ ├── configureClient.ts │ │ ├── graphql/ │ │ │ ├── mutation/ │ │ │ │ ├── createUser.ts │ │ │ │ └── updateUser.ts │ │ │ ├── query/ │ │ │ │ ├── login.ts │ │ │ │ └── user.ts │ │ │ └── subscription/ │ │ │ └── users.ts │ │ └── utils/ │ │ ├── auth.tsx │ │ └── validation.ts │ └── tsconfig.json └── react-app/ ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── package.json ├── public/ │ ├── index.html │ └── manifest.json ├── src/ │ ├── App.test.tsx │ ├── App.tsx │ ├── components/ │ │ ├── Card/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── Footer/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── List/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ └── LoginForm/ │ │ ├── index.tsx │ │ └── styles.scss │ ├── config/ │ │ └── index.ts │ ├── configureClient.ts │ ├── graphql/ │ │ ├── mutation/ │ │ │ ├── createUser.ts │ │ │ └── updateUser.ts │ │ ├── query/ │ │ │ ├── login.ts │ │ │ └── user.ts │ │ └── subscription/ │ │ └── users.ts │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── screens/ │ │ ├── Login/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── NoMatch/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── SignUp/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── Subscription/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── Update/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ ├── Users/ │ │ │ ├── index.tsx │ │ │ └── styles.scss │ │ └── Welcome/ │ │ ├── index.tsx │ │ └── styles.scss │ ├── setupTests.ts │ └── utils/ │ ├── auth.tsx │ └── validation.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ custom: ['https://www.buymeacoffee.com/anuraggarg'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/.gitignore ================================================ node_modules yarn.lock dist ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/config/express.ts ================================================ /** * File containing Express Configuration * @author Anurag Garg */ import { ApolloServer } from 'apollo-server-express'; import cors from 'cors'; import express from 'express'; import * as http from 'http'; import schema from '../server/graphql/schema/index'; import auth from '../server/middleware/auth'; import config from './index'; class Express { public express: express.Application; public server: ApolloServer = new ApolloServer(schema); public httpServer: http.Server; public init = (): void => { /** * Creating an express application */ this.express = express(); /** * Middlerware for using CORS */ this.express.use(cors({ origin(origin, callback) { /** * Allow requests with no origin * Like mobile apps or curl requests */ if (!origin) { return callback(null, true); } if (config.allowedOrigins.indexOf(origin) === -1) { const msg = `The CORS policy for this site does not allow access from the specified Origin.`; return callback(new Error(msg), false); } return callback(null, true); } })); /** * Middlerware for extracting authToken */ this.express.use(auth); this.server.applyMiddleware({ app: this.express }); this.httpServer = http.createServer(this.express); /** * Installing subscription handlers */ this.server.installSubscriptionHandlers(this.httpServer); } } export default Express; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/config/index.ts ================================================ /** * Config file * @author Anurag Garg */ import dotenv from 'dotenv'; dotenv.config(); export default { db: process.env.DB, jwtSecret: process.env.JWT_SECRET, port: process.env.PORT, allowedOrigins: ['http://localhost:3000', 'http://yourapp.com', 'http://localhost:4020'] }; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/index.ts ================================================ /** * Bootstrap your app * @author Anurag Garg */ import Promise from 'bluebird'; import mongoose from 'mongoose'; import config from './config'; import Express from './config/express'; /** * Promisify All The Mongoose * @param mongoose */ Promise.promisifyAll(mongoose); /** * Connecting Mongoose * @param uris * @param options */ mongoose.connect(config.db, { bufferMaxEntries: 0, keepAlive: true, reconnectInterval: 500, reconnectTries: 30, socketTimeoutMS: 0, useNewUrlParser: true, useUnifiedTopology: true }); /** * Throw error when not able to connect to database */ mongoose.connection.on('error', () => { throw new Error(`unable to connect to database: ${config.db}`); }); /** * Initialize Express */ const ExpressServer = new Express(); ExpressServer.init(); /** * Listen to port */ ExpressServer.httpServer.listen(process.env.PORT || config.port, () => { console.log(`🚀 Server ready at ${config.port}`); console.log( `🚀 Server ready at http://localhost:${config.port}${ExpressServer.server.graphqlPath}` ); console.log( `🚀 Subscriptions ready at ws://localhost:${config.port}${ExpressServer.server.subscriptionsPath}` ); }); ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/package.json ================================================ { "name": "Express_GraphQL_Apollo_Mongodb_Server", "version": "1.0.0", "description": "Backend Server for Next.js GraphQL Express Apollo Boilerplate", "private": true, "main": "dist/index.js", "scripts": { "clean": "rm -rf dist", "prebuild": "tslint -c tslint.json -p tsconfig.json --fix", "build": "yarn clean && tsc", "prestart": "yarn build", "start": "tsc --watch & nodemon dist/index.js", "test": "echo \"Error: no test specified\" && exit 1", "pre-commit": "yarn tslint && yarn build", "tslint": "tslint --project tsconfig.json", "tslint:fix": "tslint --project tsconfig.json --fix" }, "keywords": [], "author": "Anurag Garg", "license": "MIT", "husky": { "hooks": { "pre-commit": "yarn pre-commit" } }, "dependencies": { "apollo-server": "^2.9.6", "apollo-server-express": "^2.9.6", "bluebird": "^3.5.5", "dotenv": "^8.2.0", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.15", "mongoose": "^5.6.13" }, "devDependencies": { "@types/bluebird": "^3.5.29", "@types/dotenv": "^8.2.0", "@types/express": "^4.17.2", "@types/jsonwebtoken": "^8.3.6", "@types/lodash": "^4.14.149", "@types/mongoose": "^5.5.41", "@types/node": "^13.1.7", "husky": "^4.2.0", "nodemon": "^1.19.2", "tslint": "^5.20.1", "typescript": "^3.7.5" } } ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/graphql/resolvers/index.ts ================================================ /** * Exporting all resolvers * @author Anurag Garg */ import { UserMutation, UserQueries, UserSubscription } from './user'; const rootResolver = { Query: { ...UserQueries // Add other queries here }, Mutation: { ...UserMutation // Add other mutations here }, Subscription: { ...UserSubscription // Add other subscriptions here } }; export default rootResolver; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/graphql/resolvers/merge.ts ================================================ /** * Primary file for extracting proper schema structured objects * @author Anurag Garg */ import dateToString from '../../helpers/date'; import User from '../../models/user'; /** * Get user object with schema typing * @param id */ const getUser = async (id: string) => { try { const user: any = await User.findById(id); return { ...user._doc, _id: user.id, createdAt: dateToString(user._doc.createdAt), updatedAt: dateToString(user._doc.updatedAt) }; } catch (err) { throw err; } }; /** * Get user object with schema typing * @param user */ const transformUser = (user: any) => { return { ...user._doc, _id: user.id, createdAt: dateToString(user._doc.createdAt), updatedAt: dateToString(user._doc.updatedAt) }; }; export { getUser, transformUser }; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/graphql/resolvers/user.ts ================================================ /** * File containing all user queries, mutations and subscriptions * @author Anurag Garg */ import { PubSub } from 'apollo-server'; import jwt from 'jsonwebtoken'; import mongoose from 'mongoose'; import config from '../../../config'; import User from '../../models/user'; import { transformUser } from './merge'; const pubsub = new PubSub(); const USER_ADDED = 'USER_ADDED'; /** * User Queries */ const UserQueries = { users: async (parent, args, context) => { try { const users = await User.find(); return users.map((user) => { return transformUser(user); }); } catch (err) { throw err; } }, user: async (parent, { userId }) => { try { const user = await User.findById(userId); return transformUser(user); } catch (err) { throw err; } }, login: async (parent, { email, password }) => { try { const user: any = await User.findOne({ email, password }); if (!user) { throw new Error('User does not Exists'); } const token = jwt.sign({ userId: user.id }, config.jwtSecret, { expiresIn: '1h' }); return { userId: user.id, token, tokenExpiration: 1 }; } catch (err) { throw err; } } }; /** * User Mutations */ const UserMutation = { createUser: async (parent: any, { userInput }: any) => { try { const user = await User.findOne({ email: userInput.email }); if (user) { throw new Error('User already Exists'); } else { const newUser = new User({ _id: new mongoose.Types.ObjectId(), email: userInput.email, name: userInput.name, password: userInput.password }); const savedUser = await newUser.save(); pubsub.publish(USER_ADDED, { userAdded: transformUser(savedUser) }); const token = jwt.sign({ userId: savedUser.id }, config.jwtSecret, { expiresIn: '1h' }); return { userId: savedUser.id, token, tokenExpiration: 1 }; } } catch (error) { throw error; } }, updateUser: async (parent, { userId, updateUser }, context) => { // If not authenticated throw error if (!context.isAuth) { throw new Error('Non Authenticated'); } try { const user = await User.findByIdAndUpdate(userId, updateUser, { new: true }); return transformUser(user); } catch (error) { throw error; } } }; /** * User Subscriptions */ const UserSubscription = { userAdded: { subscribe: () => pubsub.asyncIterator([USER_ADDED]) } }; export { UserQueries, UserMutation, UserSubscription }; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/graphql/schema/index.ts ================================================ /** * Primary file for GraphQL Schema * @author Anurag Garg */ import { gql } from 'apollo-server-express'; import { ApolloServerExpressConfig } from 'apollo-server-express'; import resolvers from '../resolvers/index'; const typeDefs = gql` type Query { users: [User!]! user(userId: ID!): User! login(email: String!, password: String!): AuthData! } type Mutation { createUser(userInput: UserInput): AuthData! updateUser(userId: ID!, updateUser: UpdateUser): User! } type Subscription { userAdded: User } type User { _id: ID! email: String! name: String! password: String createdAt: String! updatedAt: String! } type AuthData { userId: ID! token: String! tokenExpiration: Int! } input UserInput { email: String! name: String! password: String! } input UpdateUser { email: String name: String password: String } `; const schema: ApolloServerExpressConfig = { typeDefs, resolvers, introspection: true, context: async ({ req, connection, payload }: any) => { if (connection) { return { isAuth: payload.authToken }; } return { isAuth: req.isAuth }; }, playground: true }; export default schema; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/helpers/date.ts ================================================ /** * Define nethod for converting date to string * @author Anurag Garg */ const dateToString = (date: Date) => new Date(date).toISOString(); export default dateToString; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/middleware/auth.ts ================================================ /** * Define middlerware for extracting authToken * @author Anurag Garg */ import * as jwt from 'jsonwebtoken'; import config from '../../config'; export default (req: any, res: any, next: any) => { const authHeader = req.get('Authorization'); if (!authHeader) { req.isAuth = false; return next(); } const token = authHeader.split(' ')[1]; if (!token || token === '') { req.isAuth = false; return next(); } let decodedToken: any; try { decodedToken = jwt.verify(token, config.jwtSecret); } catch (err) { req.isAuth = false; return next(); } if (!decodedToken) { req.isAuth = false; return next(); } req.isAuth = true; req.userId = decodedToken.userId; return next(); }; ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/server/models/user.ts ================================================ /** * Define model for user * @author Anurag Garg */ import mongoose from 'mongoose'; /** * User Schema */ const userSchema = new mongoose.Schema( { _id: mongoose.Schema.Types.ObjectId, email: { type: String, required: true }, name: { type: String, required: true }, password: { type: String } }, { timestamps: true } ); /** * Statics */ userSchema.statics = { /** * Get User * @param {ObjectId} id - The objectId of user. */ get(id: string): mongoose.Document { return this.findById(id) .execAsync() .then((user: any) => { if (user) { return user; } }); } }; export default mongoose.model('User', userSchema); ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/tsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs", "pretty": true, "sourceMap": true, "outDir": "dist", "importHelpers": true, "strict": true, "noImplicitAny": false, "strictNullChecks": false, "noImplicitThis": true, "alwaysStrict": true, "noUnusedLocals": true, "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "moduleResolution": "node", "baseUrl": ".", "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, "esModuleInterop": true, "lib": [ "es5", "es6", "dom", "es2015.core", "es2015.collection", "es2015.generator", "es2015.iterable", "es2015.promise", "es2015.proxy", "es2015.reflect", "es2015.symbol", "es2015.symbol.wellknown", "esnext.asynciterable" ] }, "include": ["server/**/*", "config/**/*", "index.ts"] } ================================================ FILE: Express_GraphQL_Apollo_Mongodb_Server/tslint.json ================================================ { "defaultSeverity": "error", "extends": [ "tslint:recommended" ], "rules": { "no-trailing-whitespace": [true, "always"], "prefer-const": [true, { "destructuring": "any" }], "semicolon": [true, "always"], "quotemark": [true, "single"], "trailing-comma": [ true, { "singleline": "never", "multiline": "never" } ], "object-literal-sort-keys": false, "no-console": false }, "linterOptions": { "exclude": [ "node_modules/**", "dist/**/*.ts" ] }, "rulesDirectory": [] } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Anurag Garg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Anurag Garg

Next.js React GraphQL Express Apollo Boilerplate

Performance oriented Next.js and React.js application boilerplate with Typescript, Express.js, GraphQL, Apollo and Sass

build build build build

## Table of Contents - [Table of Contents](#table-of-contents) - [Installation](#installation) - [Step 1: Set up the Development Environment](#step-1-set-up-the-development-environment) - [Step 2: Set up Env](#step-2-set-up-env) - [Step 3: Install dependencies](#step-3-install-dependencies) - [Step 4: Running Locally](#step-4-running-locally) - [Step 5: Deployment](#step-5-deployment) - [Features](#features) - [GraphQL](#graphql) - [Express](#express) - [Next.js](#nextjs) - [React](#react) - [React Apollo](#react-apollo) - [Typescript](#typescript) - [JsonWebToken](#jsonwebtoken) - [TSLint](#tslint) - [Husky](#husky) - [Bluebird](#bluebird) - [Cors](#cors) - [Contributing](#contributing) - [Step 1](#step-1) - [Step 2](#step-2) - [Step 3](#step-3) - [Support](#support) - [Donations](#donations) - [License](#license) ## Installation Clone this repo to your local machine using `https://github.com/garganurag893/Next.js_GraphQL_Express_Apollo_Boilerplate` ### Step 1: Set up the Development Environment You need to set up your development environment before you can do anything. **Install [Node.js and NPM](https://nodejs.org/en/download/)** - on OSX use [homebrew](http://brew.sh) `brew install node` - on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs` **Install yarn globally** ```bash yarn global add yarn ``` > NOTE : If you work with a mac, we recommend to use homebrew for the installation. **Install MongoDB** Once Brew is installed, it is time to install MongoDB by issuing the following command on the Terminal: ```bash brew install mongodb ``` ### Step 2: Set up Env Open .env file in a editor and add your configuration for database and other required fields. ```ts NODE_ENV = development; JWT_SECRET = "somesuperkey"; DB = "mongodb://localhost/nextjs_graphql_express_apollo_boilerplate_development"; PORT = 4020; ``` ### Step 3: Install dependencies Navigate to the server, nextjs and react app directories and run the below command: ```bash $ yarn ``` ### Step 4: Running Locally Navigate to the **Express Server** directory and run the below command in your terminal : ```bash $ yarn start ``` Now navigate to **Nextjs App** directory and run the below command in your terminal : ```bash $ yarn dev ``` Now navigate to **React App** directory and run the below command in your terminal : ```bash $ yarn start ``` ### Step 5: Deployment To deploy with ZEIT Now through your terminal, you will need to install Now CLI, a frequently updated, and open-source, command-line interface. You can get Now CLI from either npm or Yarn. Using npm, run the following command from your terminal: ```bash $ npm i -g now ``` To verify that you have installed Now CLI, try running now help from your terminal. With Now CLI installed, you can now login using the following command: ```bash $ now login ``` Navigate to **Nextjs App** directory and run the below commands in order : ```bash $ now ``` Once deployed, you will get a preview URL that is assigned on each deployment to share the latest changes under the same address. ## Features ### GraphQL GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. ### Express Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. ### Next.js Next.js extends React to provide a powerful method for loading a page's initial data, no matter where it is coming from. With a single place to prepopulate page context, server-side rendering with Next.js seamlessly integrates with any existing data-fetching strategy. ### React React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. ### React Apollo React Apollo allows you to fetch data from your GraphQL server and use it in building complex and reactive UIs using the React framework. React Apollo may be used in any context that React may be used. In the browser, in React Native, or in Node.js when you want to do server-side rendering. ### Typescript TypeScript is an open-source programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript, and adds optional static typing to the language. TypeScript is designed for development of large applications and transcompiles to JavaScript. ### JsonWebToken JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. ### TSLint TSLint is an extensible static analysis tool that checks TypeScript code for readability, maintainability, and functionality errors ### Husky Husky can prevent bad git commit, git push and more 🐶 woof! ### Bluebird Bluebird is a fully featured promise library with focus on innovative features and performance. ### Cors Cross-origin resource sharing is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served ## Contributing > To get started... ### Step 1 - **Option 1** - 🍴 Fork this repo! - **Option 2** - 👯 Clone this repo to your local machine using `https://github.com/garganurag893/Next.js_GraphQL_Express_Apollo_Boilerplate` ### Step 2 - **HACK AWAY!** 🔨🔨🔨 ### Step 3 - 🔃 Create a new pull request using `https://github.com/garganurag893/Next.js_GraphQL_Express_Apollo_Boilerplate`. ## Support Reach out to me at one of the following places! - Twitter at https://twitter.com/AnuragG94634191 - Medium at https://medium.com/@garganurag893 - Instagram at https://www.instagram.com/the_only_anurag/ - Email at garganurag893@gmail.com ## Donations If this boilerplate help save your valuable time and you feel to help me donate now to help me create more amazing stuff. [![Support via Paypal](https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-200px.png)](https://paypal.me/garganurag893?locale.x=en_GB)

Support via GooglePay

+919468026011
## License [![License](http://img.shields.io/:license-mit-blue.svg?style=flat-square)](http://badges.mit-license.org) - **[MIT license](http://opensource.org/licenses/mit-license.php)** - Copyright 2020 © Anurag Garg. ================================================ FILE: nextjs-app/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', extends: [ 'plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint', 'react-app', 'plugin:prettier/recommended', ], plugins: ['@typescript-eslint', 'react'], rules: { "@typescript-eslint/explicit-function-return-type":0, "@typescript-eslint/no-explicit-any":0, }, }; ================================================ FILE: nextjs-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store .env* # debug npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: nextjs-app/.prettierrc.js ================================================ module.exports = { useTabs: false, printWidth: 80, singleQuote: true, trailingComma: 'es5', jsxBracketSameLine: true, noSemi: false }; ================================================ FILE: nextjs-app/next-env.d.ts ================================================ /// /// ================================================ FILE: nextjs-app/package.json ================================================ { "name": "nextjs-graphql-apollo-client", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint './**/*.{ts,js,tsx}'" }, "author": "Anurag Garg", "license": "MIT", "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "./**/*.{js,jsx,ts,tsx,css,scss}": [ "prettier --write", "yarn lint" ] }, "dependencies": { "@apollo/react-components": "^3.1.3", "@apollo/react-hooks": "^3.1.3", "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", "apollo-link-batch-http": "^1.2.13", "apollo-link-http": "^1.5.16", "apollo-link-ws": "^1.0.19", "graphql": "^14.5.8", "graphql-tag": "^2.10.1", "isomorphic-unfetch": "^3.0.0", "js-cookie": "^2.2.1", "next": "9.2.0", "next-cookies": "^2.0.3", "next-with-apollo": "^4.3.0", "react": "16.12.0", "react-apollo": "^3.1.3", "react-dom": "16.12.0", "react-toastify": "^5.5.0", "subscriptions-transport-ws": "^0.9.16" }, "devDependencies": { "@types/next": "^9.0.0", "@types/node": "^13.1.8", "@types/react": "^16.9.17", "@typescript-eslint/eslint-plugin": "^2.16.0", "@typescript-eslint/parser": "^2.16.0", "babel-eslint": "^10.0.3", "eslint": "^6.8.0", "eslint-config-prettier": "^6.9.0", "eslint-config-react-app": "^5.1.0", "eslint-plugin-flowtype": "^4", "eslint-plugin-import": "^2.20.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.18.0", "eslint-plugin-react-hooks": "^2.3.0", "husky": "^4.2.0", "lint-staged": "^10.0.2", "prettier": "^1.19.1", "typescript": "^3.7.5" } } ================================================ FILE: nextjs-app/pages/_app.tsx ================================================ /** * App Configuration * @author Anurag Garg */ import React from 'react'; import App from 'next/app'; import { ApolloProvider } from '@apollo/react-hooks'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import withData from '../src/configureClient'; class MyApp extends App { render() { const { Component, pageProps, apollo } = this.props; return ( ); } } // Wraps all components in the tree with the data provider export default withData(MyApp); ================================================ FILE: nextjs-app/pages/index.tsx ================================================ /** * Main Page * @author Anurag Garg */ import React from 'react'; import Login from '../src/components/Login'; import Footer from '../src/components/Footer'; const Home: React.SFC = () => { return (

Welcome

); }; export default Home; ================================================ FILE: nextjs-app/pages/signup.tsx ================================================ /** * SignUp Page * @author Anurag Garg */ import React from 'react'; import Router from 'next/router'; import { toast } from 'react-toastify'; import Footer from '../src/components/Footer'; import Cookies from 'js-cookie'; import { Mutation } from '@apollo/react-components'; import CREATE_USER from '../src/graphql/mutation/createUser'; import { setToken } from '../src/configureClient'; import { validateEmail } from '../src/utils/validation'; interface SignUpState { [key: string]: any; name: string; email: string; password: string; } class SignUp extends React.PureComponent { constructor(props) { super(props); this.state = { name: '', email: '', password: '', }; } handleChange = (event: any) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = async (createUser, event) => { try { event.preventDefault(); const { state } = this; if (validateEmail(state.email)) { const data = await createUser({ variables: { userInput: { ...state }, }, }); const { token, userId } = data.createUser; setToken(token); Cookies.set('userId', userId, { expires: 7 }); Router.replace('/welcome'); } else { toast.error('Invalid Email'); } } catch (error) { toast.error('Check your connection'); } }; render() { const { state } = this; return (

Sign Up

{(createUser, { loading, error }) => (
this.handleSubmit(createUser, event)} className="signup-form">
)}
); } } export default SignUp; ================================================ FILE: nextjs-app/pages/subscription.tsx ================================================ /** * Subscription Page * @author Anurag Garg */ import React from 'react'; import { useSubscription } from '@apollo/react-hooks'; import { UserCard } from '../src/components/Card'; import USER_ADDED from '../src/graphql/subscription/users'; import { withAuthSync } from '../src/utils/auth'; const Subscription = () => { const { data, loading, error } = useSubscription(USER_ADDED); let message = 'New User'; if (loading) message = 'Listening...'; if (error) message = `Error! ${error.message}`; if (data && data.userAdded.length <= 0) message = 'No New User Added'; return (

{message}

{data && data.userAdded && (
)}
); }; export default withAuthSync(Subscription); ================================================ FILE: nextjs-app/pages/update.tsx ================================================ /** * Update Profile Page * @author Anurag Garg */ import React from 'react'; import Router from 'next/router'; import { toast } from 'react-toastify'; import Footer from '../src/components/Footer'; import { Mutation } from '@apollo/react-components'; import { validateEmail } from '../src/utils/validation'; import UPDATE_USER from '../src/graphql/mutation/updateUser'; import { withAuthSync } from '../src/utils/auth'; interface UpdateState { [key: string]: any; name: string; email: string; password: string; } class Update extends React.PureComponent { constructor(props) { super(props); this.state = { name: '', email: '', password: '', }; } handleChange = (event: any) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = async (updateUser, event) => { try { event.preventDefault(); const { state, props } = this; if (validateEmail(state.email)) { await updateUser({ variables: { userId: props.userId, updateUser: { ...state }, }, }); toast.success('Profile Updated'); Router.push('/welcome'); } else { toast.error('Invalid Email'); } } catch (error) { toast.error('Check your connection'); } }; render() { const { state } = this; return (

Update Profile

{updateUser => (
this.handleSubmit(updateUser, event)} className="update-form">
)}
); } } export default withAuthSync(Update); ================================================ FILE: nextjs-app/pages/users.tsx ================================================ /** * User List Page * @author Anurag Garg */ import React from 'react'; import List from '../src/components/List'; import GET_USERS from '../src/graphql/query/user'; import { withAuthSync } from '../src/utils/auth'; interface User { name: string; _id: string; email: string; } interface Data { users: [User]; } interface UserProps { loading: boolean; data: Data; error: string; } const Users = (props: UserProps) => { const { loading, error, data } = props; let message = 'Users'; if (loading) message = 'Loading...'; if (error) message = `Error! ${error}`; if (data && data.users.length <= 0) message = 'No Users'; return (

{message}

{data && data.users.length > 0 && (
)}
); }; Users.getInitialProps = async ctx => { try { const { data, loading } = await ctx.apolloClient.query({ query: GET_USERS, }); return { data, loading }; } catch (error) { return { error: 'Failed to fetch', }; } }; export default withAuthSync(Users); ================================================ FILE: nextjs-app/pages/welcome.tsx ================================================ /** * Welcome Page * @author Anurag Garg */ import React from 'react'; import Card from '../src/components/Card'; import Footer from '../src/components/Footer'; import { withAuthSync } from '../src/utils/auth'; const Welcome: React.SFC = () => { return (

Welcome

); }; export default withAuthSync(Welcome); ================================================ FILE: nextjs-app/src/components/Card.tsx ================================================ /** * Card Component * @author Anurag Garg */ import React from 'react'; import Link from 'next/link'; interface User { name: string; _id: string; email: string; } interface CardProps { title: string; href: string; } interface UserCardProps { user: User; img: string; } const Card: React.SFC = props => { return (

{props.title}

); }; export const UserCard: React.SFC = props => { return (
user

{props.user.name}

{props.user.email}

); }; export default Card; ================================================ FILE: nextjs-app/src/components/Footer.tsx ================================================ /** * Footer Component * @author Anurag Garg */ import React from 'react'; const Footer: React.SFC = props => { return (

© Copyright 2020{' '} Anurag Garg

); }; export default Footer; ================================================ FILE: nextjs-app/src/components/List.tsx ================================================ /** * List Component * @author Anurag Garg */ import React from 'react'; import { UserCard } from './Card'; interface User { name: string; _id: string; email: string; } interface ListProps { data: [User]; img: string; } const List: React.SFC = props => { return ( <> {props.data.map((user: User) => ( ))} ); }; export default List; ================================================ FILE: nextjs-app/src/components/Login.tsx ================================================ /** * Login Component * @author Anurag Garg */ import React from 'react'; import Router from 'next/router'; import { toast } from 'react-toastify'; import { ApolloConsumer } from 'react-apollo'; import LOGIN_USER from '../graphql/query/login'; import { validateEmail } from '../utils/validation'; import { setToken } from '../configureClient'; import Cookies from 'js-cookie'; interface LoginState { [key: string]: any; email: string; password: string; } class Login extends React.PureComponent { constructor(props) { super(props); this.state = { email: '', password: '', }; } handleChange = (event: any) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = async (event: any, client: any) => { try { event.preventDefault(); const { state } = this; if (validateEmail(state.email)) { const { data } = await client.query({ query: LOGIN_USER, variables: { ...state }, }); const { token, userId } = data.login; setToken(token); Cookies.set('userId', userId, { expires: 7 }); Router.replace('/welcome'); } else { toast.error('Invalid Email'); } } catch (error) { toast.error('Not Authenticated'); } }; render() { const { state } = this; return ( {client => (
this.handleSubmit(e, client)} className="login-form">

Router.push('/signup')}> New user ? Sign Up

)}
); } } export default Login; ================================================ FILE: nextjs-app/src/config/index.ts ================================================ /** * Configuration * @author Anurag Garg */ export const SERVER = 'http://localhost:4020/graphql'; export const WEB_SOCKET_LINK = 'ws://localhost:4020/graphql'; ================================================ FILE: nextjs-app/src/configureClient.ts ================================================ /** * Apollo Client Configuration * @author Anurag Garg */ import { ApolloClient } from 'apollo-client'; import { split, ApolloLink, concat } from 'apollo-link'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { getMainDefinition } from 'apollo-utilities'; import withApollo from 'next-with-apollo'; import { HttpLink } from 'apollo-link-http'; import fetch from 'isomorphic-unfetch'; import { WebSocketLink } from 'apollo-link-ws'; import Cookies from 'js-cookie'; import { SERVER, WEB_SOCKET_LINK } from './config'; interface Definintion { kind: string; operation?: string; } let authToken = null; const httpLink = new HttpLink({ fetch, uri: SERVER, }); const authMiddleware = new ApolloLink((operation, forward) => { operation.setContext({ headers: { authorization: authToken || null, }, }); // Add onto payload for WebSocket authentication (operation as any & { authToken: string | undefined }).authToken = authToken; return forward(operation); }); const webSocketLink: any = process.browser ? new WebSocketLink({ uri: WEB_SOCKET_LINK, options: { reconnect: true, }, }) : null; /** * Set Token * @param token */ export const setToken = async (token: string) => { try { authToken = token ? `Bearer ${token}` : null; Cookies.set('token', authToken, { expires: 7 }); } catch (error) { console.log(error); } }; /** * Set Token In Request * @param token */ export const setTokenInRequest = async (token: string) => { try { authToken = token ? token : null; return authToken; } catch (error) { console.log(error); } }; /** * Destroy Token * For logout purpose */ export const destroyToken = async () => { try { Cookies.remove('token'); authToken = null; } catch (error) { console.log(error); } }; const link = process.browser ? split( ({ query }) => { const { kind, operation }: Definintion = getMainDefinition(query); return kind === 'OperationDefinition' && operation === 'subscription'; }, webSocketLink, httpLink ) : httpLink; export default withApollo( ({ initialState }) => new ApolloClient({ link: concat(authMiddleware, link), cache: new InMemoryCache().restore(initialState || {}), }) ); ================================================ FILE: nextjs-app/src/graphql/mutation/createUser.ts ================================================ /** * Create User Mutation * @author Anurag Garg */ import gql from 'graphql-tag'; const CREATE_USER = gql` mutation createUser($userInput: UserInput) { createUser(userInput: $userInput) { token userId } } `; export default CREATE_USER; ================================================ FILE: nextjs-app/src/graphql/mutation/updateUser.ts ================================================ /** * Update user mutation * @author Anurag Garg */ import gql from 'graphql-tag'; const UPDATE_USER = gql` mutation updateUser($userId: ID!, $updateUser: UpdateUser) { updateUser(userId: $userId, updateUser: $updateUser) { _id name email } } `; export default UPDATE_USER; ================================================ FILE: nextjs-app/src/graphql/query/login.ts ================================================ /** * Login user query * @author Anurag Garg */ import gql from 'graphql-tag'; const LOGIN_USER = gql` query login($email: String!, $password: String!) { login(email: $email, password: $password) { token userId } } `; export default LOGIN_USER; ================================================ FILE: nextjs-app/src/graphql/query/user.ts ================================================ /** * Get all users query * @author Anurag Garg */ import gql from 'graphql-tag'; const GET_USERS = gql` { users { name _id email } } `; export default GET_USERS; ================================================ FILE: nextjs-app/src/graphql/subscription/users.ts ================================================ /** * New user added subscription * @author Anurag Garg */ import gql from 'graphql-tag'; const USER_ADDED = gql` subscription { userAdded { name _id email } } `; export default USER_ADDED; ================================================ FILE: nextjs-app/src/utils/auth.tsx ================================================ /** * Auth Middlerware Component * @author Anurag Garg */ import * as React from 'react'; import Router from 'next/router'; import nextCookie from 'next-cookies'; import { setTokenInRequest } from '../configureClient'; const getDisplayName = Component => Component.displayName || Component.name || 'Component'; export const auth = ctx => { const { token, userId } = nextCookie(ctx); if (ctx.req && !token) { ctx.res.writeHead(302, { Location: '/' }); ctx.res.end(); return; } if (!token) { Router.push('/'); } return { token, userId }; }; export const withAuthSync = WrappedComponent => class extends React.Component { static displayName = `withAuthSync(${getDisplayName(WrappedComponent)})`; static async getInitialProps(ctx) { const { token, userId } = auth(ctx); await setTokenInRequest(token); const componentProps = WrappedComponent.getInitialProps && (await WrappedComponent.getInitialProps(ctx)); return { ...componentProps, token, userId }; } render() { return ; } }; ================================================ FILE: nextjs-app/src/utils/validation.ts ================================================ /** * Email Validation * @author Anurag Garg */ const validateEmail = (email: string): boolean => { if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) { return true; } return false; }; export { validateEmail }; ================================================ FILE: nextjs-app/tsconfig.json ================================================ { "compilerOptions": { "target": "es6", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "exclude": [ "node_modules" ], "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ] } ================================================ FILE: react-app/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', extends: [ 'plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint', 'react-app', 'plugin:prettier/recommended', ], plugins: ['@typescript-eslint', 'react'], rules: { '@typescript-eslint/explicit-function-return-type': 0, '@typescript-eslint/no-explicit-any': 0, }, }; ================================================ FILE: react-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: react-app/.prettierrc.js ================================================ module.exports = { useTabs: false, printWidth: 80, singleQuote: true, trailingComma: 'es5', jsxBracketSameLine: true, noSemi: false, }; ================================================ FILE: react-app/package.json ================================================ { "name": "react-app", "version": "0.1.0", "private": true, "author": "Anurag Garg", "license": "MIT", "dependencies": { "@apollo/react-components": "^3.1.3", "@apollo/react-hooks": "^3.1.3", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", "apollo-link-batch-http": "^1.2.13", "apollo-link-http": "^1.5.16", "apollo-link-ws": "^1.0.19", "graphql": "^14.5.8", "graphql-tag": "^2.10.1", "isomorphic-unfetch": "^3.0.0", "js-cookie": "^2.2.1", "node-sass": "^4.13.1", "react": "^16.12.0", "react-apollo": "^3.1.3", "react-dom": "^16.12.0", "react-router-dom": "^5.1.2", "react-scripts": "3.3.0", "react-toastify": "^5.5.0", "subscriptions-transport-ws": "^0.9.16", "typescript": "~3.7.2" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "lint": "eslint './**/*.{ts,js,tsx}'" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "./**/*.{js,jsx,ts,tsx,css}": [ "prettier --write", "yarn lint" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@types/js-cookie": "^2.2.4", "@types/react-router-dom": "^5.1.3", "@typescript-eslint/eslint-plugin": "^2.16.0", "@typescript-eslint/parser": "^2.16.0", "babel-eslint": "^10.0.3", "eslint": "^6.8.0", "eslint-config-prettier": "^6.9.0", "eslint-config-react-app": "^5.1.0", "eslint-plugin-flowtype": "^4", "eslint-plugin-import": "^2.20.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.18.0", "eslint-plugin-react-hooks": "^2.3.0", "husky": "^4.2.0", "lint-staged": "^10.0.2", "prettier": "^1.19.1" } } ================================================ FILE: react-app/public/index.html ================================================ React App
================================================ FILE: react-app/public/manifest.json ================================================ { "short_name": "React GraphQL Boilerplate App", "name": "React GraphQL Boilerplate App", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: react-app/src/App.test.tsx ================================================ /** * Test File * @author Anurag Garg */ import React from 'react'; import { render } from '@testing-library/react'; import App from './App'; test('renders welcome heading', () => { const { getByText } = render(); const linkElement = getByText(/Welcome/i); expect(linkElement).toBeInTheDocument(); }); ================================================ FILE: react-app/src/App.tsx ================================================ /** * App Component * @author Anurag Garg */ import React from 'react'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { ApolloProvider } from '@apollo/react-hooks'; import apolloClient from './configureClient'; import PrivateRoute from './utils/auth'; import Login from './screens/Login'; import SignUp from './screens/SignUp'; import Welcome from './screens/Welcome'; import Users from './screens/Users'; import Update from './screens/Update'; import NoMatch from './screens/NoMatch'; import Subscription from './screens/Subscription'; const App = () => { return ( ); }; export default App; ================================================ FILE: react-app/src/components/Card/index.tsx ================================================ /** * Card Component * @author Anurag Garg */ import React from 'react'; import './styles.scss'; import { Link } from 'react-router-dom'; interface User { name: string; _id: string; email: string; } interface CardProps { title: string; href: string; } interface UserCardProps { user: User; img: string; } const Card: React.SFC = props => { return (

{props.title}

); }; export const UserCard: React.SFC = props => { return (
user

{props.user.name}

{props.user.email}

); }; export default Card; ================================================ FILE: react-app/src/components/Card/styles.scss ================================================ .card { display: flex; height: 5rem; width: 20rem; justify-content: center; padding: 2rem; align-self: stretch; align-items: center; background-color: white; margin: 2rem; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); transition: transform 0.2s; cursor: pointer; &:hover { transform: scale(1.1); } h2 { font-size: 2rem; } } .listCard { display: flex; flex-flow: wrap row; justify-content: space-between; padding: 2rem; -webkit-align-self: stretch; align-self: stretch; align-items: center; background-color: white; margin: 2rem; width: 20rem; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); transition: transform 0.2s; .userCardDetails { display: flex; flex-flow: wrap column; justify-content: space-between; align-items: flex-end; height: 3rem; } h2 { font-size: 1rem; text-align: right; } img { margin-right: 2rem; } } .link { text-decoration: none; color: black; } ================================================ FILE: react-app/src/components/Footer/index.tsx ================================================ /** * Footer Component * @author Anurag Garg */ import React from 'react'; import './styles.scss'; const Footer: React.SFC = props => { return ( ); }; export default Footer; ================================================ FILE: react-app/src/components/Footer/styles.scss ================================================ .footer { color: rgba(255, 255, 255, 0.5); margin-top: auto !important; .footer-text-container { font-size: 1rem; font-weight: 400; line-height: 1.5; text-shadow: 0 0.05rem 0.1rem rgba(0, 0, 0, 0.5); text-align: center !important; color: rgba(255, 255, 255, 0.5); } a { text-decoration: none; color: white; } } ================================================ FILE: react-app/src/components/List/index.tsx ================================================ /** * List Component * @author Anurag Garg */ import React from 'react'; import { UserCard } from '../Card'; import './styles.scss'; interface User { name: string; _id: string; email: string; } interface ListProps { data: [User]; img: string; } const List: React.SFC = props => { return ( <> {props.data.map((user: User) => ( ))} ); }; export default List; ================================================ FILE: react-app/src/components/List/styles.scss ================================================ .List { &:hove { transform: scale(1.1); } h2 { font-size: 1rem; text-align: right; } img { margin-right: 2rem; } } ================================================ FILE: react-app/src/components/LoginForm/index.tsx ================================================ /** * Login Form Component * @author Anurag Garg */ import React from 'react'; import { toast } from 'react-toastify'; import { ApolloConsumer } from 'react-apollo'; import LOGIN_USER from '../../graphql/query/login'; import { validateEmail } from '../../utils/validation'; import { setToken } from '../../configureClient'; import { Link } from 'react-router-dom'; import Cookies from 'js-cookie'; import './styles.scss'; interface LoginFormState { [key: string]: any; email: string; password: string; } class LoginForm extends React.PureComponent { constructor(props: any) { super(props); this.state = { email: '', password: '', }; } handleChange = (event: any) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = async (event: any, client: any) => { try { event.preventDefault(); const { state, props } = this; if (validateEmail(state.email)) { const { data } = await client.query({ query: LOGIN_USER, variables: { ...state }, }); const { token, userId } = data.login; setToken(token); Cookies.set('userId', userId, { expires: 7 }); props.history.replace('/welcome'); } else { toast.error('Invalid Email'); } } catch (error) { toast.error('Not Authenticated'); } }; render() { const { state } = this; return ( {client => (
this.handleSubmit(e, client)} className="login-form">

New user ? Sign Up

)}
); } } export default LoginForm; ================================================ FILE: react-app/src/components/LoginForm/styles.scss ================================================ .login-form { display: flex; flex-flow: column wrap; justify-content: center; align-items: center; border-left: 1px solid white; padding: 2rem; p { color: white; cursor: pointer; } } .login-input-box { background-color: white; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); border: 0; width: 15rem; padding: 0.5rem; height: 2rem; margin-bottom: 2rem; font-family: Candara; } .login-button { background-color: white; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); width: 15rem; font-size: 0.9rem; padding: 0.5rem; height: 3rem; margin: 2rem; font-family: Candara; transition: transform 0.2s; cursor: pointer; } .login-button:hover { transform: scale(1.1); background: #355c7d; background: -webkit-linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); background: linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); color: white; } .signup-link { text-decoration: none; } @media only screen and (max-width: 740px) { .login-form { border-left: none; border-top: 1px solid white; } } input:focus { outline: none; } ================================================ FILE: react-app/src/config/index.ts ================================================ /** * Configuration * @author Anurag Garg */ export const SERVER = 'http://localhost:4020/graphql'; export const WEB_SOCKET_LINK = 'ws://localhost:4020/graphql'; ================================================ FILE: react-app/src/configureClient.ts ================================================ /** * Apollo Client Configuration * @author Anurag Garg */ import { ApolloClient } from 'apollo-client'; import { split, ApolloLink, concat } from 'apollo-link'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { getMainDefinition } from 'apollo-utilities'; import { HttpLink } from 'apollo-link-http'; import fetch from 'isomorphic-unfetch'; import { WebSocketLink } from 'apollo-link-ws'; import Cookies from 'js-cookie'; import { SERVER, WEB_SOCKET_LINK } from './config'; interface Definintion { kind: string; operation?: string; } let authToken = ''; const httpLink = new HttpLink({ fetch, uri: SERVER, }); const authMiddleware = new ApolloLink((operation, forward) => { operation.setContext({ headers: { authorization: authToken || null, }, }); // Add onto payload for WebSocket authentication (operation as any & { authToken: string | undefined }).authToken = authToken; return forward(operation); }); const webSocketLink: WebSocketLink = new WebSocketLink({ uri: WEB_SOCKET_LINK, options: { reconnect: true, }, }); /** * Set Token * @param token */ export const setToken = async (token: string | undefined) => { try { authToken = token ? `Bearer ${token}` : ''; Cookies.set('token', authToken, { expires: 7 }); } catch (error) { console.log(error); } }; /** * Get Token & Set Token In Request */ export const getToken = async () => { try { const token = Cookies.get('token'); authToken = token ? token : ''; return authToken; } catch (error) { console.log(error); } }; /** * Destroy Token * For logout purpose */ export const destroyToken = async () => { try { Cookies.remove('token'); authToken = ''; } catch (error) { console.log(error); } }; const link = split( ({ query }) => { const { kind, operation }: Definintion = getMainDefinition(query); return kind === 'OperationDefinition' && operation === 'subscription'; }, webSocketLink, httpLink ); const client = new ApolloClient({ link: concat(authMiddleware, link), cache: new InMemoryCache().restore({}), connectToDevTools: true, }); export default client; ================================================ FILE: react-app/src/graphql/mutation/createUser.ts ================================================ /** * Create User Mutation * @author Anurag Garg */ import gql from 'graphql-tag'; const CREATE_USER = gql` mutation createUser($userInput: UserInput) { createUser(userInput: $userInput) { token userId } } `; export default CREATE_USER; ================================================ FILE: react-app/src/graphql/mutation/updateUser.ts ================================================ /** * Update user mutation * @author Anurag Garg */ import gql from 'graphql-tag'; const UPDATE_USER = gql` mutation updateUser($userId: ID!, $updateUser: UpdateUser) { updateUser(userId: $userId, updateUser: $updateUser) { _id name email } } `; export default UPDATE_USER; ================================================ FILE: react-app/src/graphql/query/login.ts ================================================ /** * Login user query * @author Anurag Garg */ import gql from 'graphql-tag'; const LOGIN_USER = gql` query login($email: String!, $password: String!) { login(email: $email, password: $password) { token userId } } `; export default LOGIN_USER; ================================================ FILE: react-app/src/graphql/query/user.ts ================================================ /** * Get all users query * @author Anurag Garg */ import gql from 'graphql-tag'; const GET_USERS = gql` { users { name _id email } } `; export default GET_USERS; ================================================ FILE: react-app/src/graphql/subscription/users.ts ================================================ /** * New user added subscription * @author Anurag Garg */ import gql from 'graphql-tag'; const USER_ADDED = gql` subscription { userAdded { name _id email } } `; export default USER_ADDED; ================================================ FILE: react-app/src/index.css ================================================ h1, h2 { margin: 0; font-family: Candara; } body { margin: 0; padding: 0; height: 100%; min-height: 100vh; font-family: Candara; background: #355c7d; background: -webkit-linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); background: linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); } ================================================ FILE: react-app/src/index.tsx ================================================ /** * Primary file for react * @author Anurag Garg */ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render(, document.getElementById('root')); ================================================ FILE: react-app/src/react-app-env.d.ts ================================================ /// ================================================ FILE: react-app/src/screens/Login/index.tsx ================================================ /** * Main Page * @author Anurag Garg */ import React from 'react'; import LoginForm from '../../components/LoginForm'; import Footer from '../../components/Footer'; import './styles.scss'; const Login: React.SFC = (props: any) => { return (

Welcome

); }; export default Login; ================================================ FILE: react-app/src/screens/Login/styles.scss ================================================ .loginPage-mainContainer { height: 100%; min-height: 100vh; width: 100%; .loginPage-container { display: flex; justify-content: center; align-items: center; flex-flow: row wrap; padding: 8rem 0rem !important; } .heading { color: white; text-align: center; font-size: 5rem; padding: 2rem; } @media only screen and (max-width: 740px) { .loginPage-container { padding: 3rem 0rem !important; } } } ================================================ FILE: react-app/src/screens/NoMatch/index.tsx ================================================ /** * No Match Page * @author Anurag Garg */ import React from 'react'; import Footer from '../../components/Footer'; import './styles.scss'; const NoMatch: React.SFC = () => { return (

No Match Found | 404

); }; export default NoMatch; ================================================ FILE: react-app/src/screens/NoMatch/styles.scss ================================================ .nomatch-container { height: 100%; min-height: 100vh; width: 100%; display: flex; justify-content: center; align-items: center; flex-flow: column wrap; } .nomatch-heading { color: white; text-align: center; font-size: 5rem; padding: 5rem 0rem; } ================================================ FILE: react-app/src/screens/SignUp/index.tsx ================================================ /** * SignUp Page * @author Anurag Garg */ import React from 'react'; import { toast } from 'react-toastify'; import Footer from '../../components/Footer'; import Cookies from 'js-cookie'; import { Mutation } from '@apollo/react-components'; import CREATE_USER from '../../graphql/mutation/createUser'; import { setToken } from '../../configureClient'; import { validateEmail } from '../../utils/validation'; import './styles.scss'; interface SignUpState { [key: string]: any; name: string; email: string; password: string; } class SignUp extends React.PureComponent { constructor(props: any) { super(props); this.state = { name: '', email: '', password: '', }; } handleChange = (event: any) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = async (createUser: any, event: any) => { try { event.preventDefault(); const { state, props } = this; if (validateEmail(state.email)) { const data = await createUser({ variables: { userInput: { ...state }, }, }); const { token, userId } = data.createUser; setToken(token); Cookies.set('userId', userId, { expires: 7 }); props.history.replace('/welcome'); } else { toast.error('Invalid Email'); } } catch (error) { toast.error('Check your connection'); } }; render() { const { state } = this; return (

Sign Up

{(createUser: any) => (
this.handleSubmit(createUser, event)} className="signup-form">
)}
); } } export default SignUp; ================================================ FILE: react-app/src/screens/SignUp/styles.scss ================================================ .signup-container { height: 100%; min-height: 100vh; width: 100%; display: flex; justify-content: center; align-items: center; flex-flow: column wrap; } .signup-heading { color: white; text-align: center; font-size: 5rem; padding: 5rem 0rem; } .signup-form { display: flex; flex-flow: column wrap; justify-content: center; align-items: center; } .signup-input-box { background-color: white; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); border: 0; width: 15rem; padding: 0.5rem; height: 2rem; margin-bottom: 2rem; font-family: Candara; } .signup-button { background-color: white; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); width: 15rem; font-size: 0.9rem; padding: 0.5rem; height: 3rem; margin: 2rem; font-family: Candara; transition: transform 0.2s; cursor: pointer; &:hover { transform: scale(1.1); background: #355c7d; background: -webkit-linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); background: linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); color: white; } } input:focus { outline: none; } ================================================ FILE: react-app/src/screens/Subscription/index.tsx ================================================ /** * Subscription Page * @author Anurag Garg */ import React from 'react'; import { useSubscription } from '@apollo/react-hooks'; import { UserCard } from '../../components/Card'; import USER_ADDED from '../../graphql/subscription/users'; import './styles.scss'; const Subscription = () => { const { data, loading, error } = useSubscription(USER_ADDED); let message = 'New User'; if (loading) message = 'Listening...'; if (error) message = `Error! ${error.message}`; if (data && data.userAdded.length <= 0) message = 'No New User Added'; return (

{message}

{data && data.userAdded && (
)}
); }; export default Subscription; ================================================ FILE: react-app/src/screens/Subscription/styles.scss ================================================ .subscription-container { height: 100%; min-height: 100vh; width: 100%; display: flex; justify-content: center; align-items: center; flex-flow: column wrap; } .subscription-listContainer { display: flex; flex-flow: wrap row; justify-content: space-evenly; align-items: center; } .subscription-heading { color: white; text-align: center; font-size: 5rem; padding: 0rem 0 5rem; } ================================================ FILE: react-app/src/screens/Update/index.tsx ================================================ /** * Update Profile Page * @author Anurag Garg */ import React from 'react'; import { toast } from 'react-toastify'; import Footer from '../../components/Footer'; import { Mutation } from '@apollo/react-components'; import { validateEmail } from '../../utils/validation'; import UPDATE_USER from '../../graphql/mutation/updateUser'; import './styles.scss'; interface UpdateState { [key: string]: any; name: string; email: string; password: string; } class Update extends React.PureComponent { constructor(props: any) { super(props); this.state = { name: '', email: '', password: '', }; } handleChange = (event: any) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = async (updateUser: any, event: any) => { try { event.preventDefault(); const { state, props } = this; if (validateEmail(state.email)) { await updateUser({ variables: { userId: props.userId, updateUser: { ...state }, }, }); toast.success('Profile Updated'); props.history.push('/welcome'); } else { toast.error('Invalid Email'); } } catch (error) { toast.error('Check your connection'); } }; render() { const { state } = this; return (

Update Profile

{(updateUser: any) => (
this.handleSubmit(updateUser, event)} className="update-form">
)}
); } } export default Update; ================================================ FILE: react-app/src/screens/Update/styles.scss ================================================ .update-container { height: 100%; min-height: 100vh; width: 100%; display: flex; justify-content: center; align-items: center; flex-flow: column wrap; } .update-heading { color: white; text-align: center; font-size: 5rem; padding: 5rem 0rem; } .update-form { display: flex; flex-flow: column wrap; justify-content: center; align-items: center; } .update-input-box { background-color: white; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); border: 0; width: 15rem; padding: 0.5rem; height: 2rem; margin-bottom: 2rem; font-family: Candara; } .update-button { background-color: white; border-radius: 14px 0px 14px 1px; -moz-border-radius: 14px 0px 14px 1px; -webkit-border-radius: 14px 0px 14px 1px; border: 0px solid #000000; box-shadow: 0 20px 30px -16px rgba(9, 9, 16, 0.2); width: 15rem; font-size: 0.9rem; padding: 0.5rem; height: 3rem; margin: 2rem; font-family: Candara; transition: transform 0.2s; cursor: pointer; } .update-button:hover { transform: scale(1.1); background: #355c7d; background: -webkit-linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); background: linear-gradient(to right, #c06c84, #6c5b7b, #355c7d); color: white; } input:focus { outline: none; } ================================================ FILE: react-app/src/screens/Users/index.tsx ================================================ /** * User List Page * @author Anurag Garg */ import React from 'react'; import List from '../../components/List'; import { useQuery } from '@apollo/react-hooks'; import GET_USERS from '../../graphql/query/user'; import './styles.scss'; const Users = () => { const { loading, error, data } = useQuery(GET_USERS); let message = 'Users'; if (loading) message = 'Loading...'; if (error) message = `Error! ${error}`; if (data && data.users.length <= 0) message = 'No Users'; return (

{message}

{data && data.users.length > 0 && (
)}
); }; export default Users; ================================================ FILE: react-app/src/screens/Users/styles.scss ================================================ .users-container { height: 100%; min-height: 100vh; width: 100%; display: flex; justify-content: center; align-items: center; flex-flow: column wrap; } .users-listContainer { display: flex; flex-flow: wrap row; justify-content: space-evenly; align-items: center; } .users-heading { color: white; text-align: center; font-size: 5rem; padding: 0rem 0 5rem; } ================================================ FILE: react-app/src/screens/Welcome/index.tsx ================================================ /** * Welcome Page * @author Anurag Garg */ import React from 'react'; import Card from '../../components/Card'; import Footer from '../../components/Footer'; import './styles.scss'; const Welcome: React.SFC = () => { return (

Welcome

); }; export default Welcome; ================================================ FILE: react-app/src/screens/Welcome/styles.scss ================================================ .welcome-container { height: 100%; min-height: 100vh; width: 100%; display: flex; justify-content: center; align-items: center; flex-flow: column wrap; } .welcome-cardContainer { display: flex; flex-flow: wrap row; justify-content: center; align-items: center; } .welcome-heading { color: white; text-align: center; font-size: 5rem; padding: 6rem 0rem; } ================================================ FILE: react-app/src/setupTests.ts ================================================ // jest-dom adds custom jest matchers for asserting on DOM nodes. // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom/extend-expect'; ================================================ FILE: react-app/src/utils/auth.tsx ================================================ /** * Auth Middlerware Component * @author Anurag Garg */ import * as React from 'react'; import { setToken } from '../configureClient'; import Cookies from 'js-cookie'; import { Route, Redirect } from 'react-router-dom'; const PrivateRoute = ({ children, ...rest }: any) => { const token = Cookies.get('token'); setToken(token); if (!token) { return ; } return ( token ? ( children ) : ( ) } /> ); }; export default PrivateRoute; ================================================ FILE: react-app/src/utils/validation.ts ================================================ /* eslint-disable no-useless-escape */ /** * Email Validation * @author Anurag Garg */ const validateEmail = (email: string): boolean => { if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) { return true; } return false; }; export { validateEmail }; ================================================ FILE: react-app/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react" }, "include": [ "src" ] }