├── .babelrc ├── .gitignore ├── README.md ├── client ├── components │ ├── AlbumCreate.js │ ├── AlbumDetail.js │ ├── AlbumsList.js │ ├── SongCreate.js │ └── SongList.js ├── index.html ├── index.js ├── queries │ ├── fetchAlbum.js │ └── fetchAlbumsList.js └── style │ └── style.css ├── demo.gif ├── index.js ├── package.json ├── server ├── models │ ├── album.js │ ├── index.js │ └── song.js ├── schema │ ├── album_type.js │ ├── mutations.js │ ├── root_query_type.js │ ├── schema.js │ └── song_type.js └── server.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-class-properties" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE 3 | .env 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating a simple CRUD app with NodeJS, MongoDB, GraphQL, React and Apollo 2 | 3 | # Description 4 | 5 | A simple Apollo + GraphQL + NodeJs + Express + MongoDB + React CRUD 6 | 7 | This repository shows a complete example of a CRUD application based on NodeJs, React with Graphql API. 8 | This is a FullStack app to cover all stages of GraphQL at server and client side. 9 | 10 | DEMO - ![](demo.gif) 11 | 12 | ### Set Up 13 | Clone and install: 14 | 15 | ```bash 16 | git clone https://github.com/hemanth09/creating-crud-app-with-apollo-graphql-node-mongodb-and-react.git 17 | cd creating-crud-app-with-apollo-graphql-node-mongodb-and-react 18 | npm/yarn install 19 | ``` 20 | 21 | Create a cluster in MongoDB Atlas and add connection string in your server.js file where prompting MONGO_URI = '' 22 | - [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) 23 | - [Create a cluster](https://docs.atlas.mongodb.com/create-new-cluster/) 24 | 25 | ### Running the Application 26 | 27 | Run it using: 28 | 29 | ```bash 30 | npm run dev 31 | ``` 32 | - And in browser App will running on http://localhost:4000/ 33 | - And Graphql server will be running on http://localhost:4000/graphql 34 | 35 | ### Project Structure 36 | 37 | . 38 | ├── client # Client folder 39 | ├── components # components folder 40 | ├── queries # queries folder 41 | └── index # index.js file 42 | ├── server # Server folder 43 | ├── models # Model folder 44 | ├── schema # Schema folder 45 | └── server # server.js file 46 | 47 | #### Server directory 48 | [http://localhost:4000/graphql](http://localhost:4000/graphql) 49 | 50 | **server.js** - This is the main file for running the server of backend. This file contains all the required modules, establish the mongoose connection and GraphQL connection. 51 | 52 | **models** - This folder contains mongoose **album** and **song** schema model that represents set of information's for album and song records in database. 53 | 54 | **schema** - And Finally this schema folder contains the most of the GraphQL logic. The schema/schema.js takes all the types and mutations 55 | - **GraphQL Types** : We need to design GraphQL user schema to specify the types for API using GraphQL schema language. Inside we got **album_type.js** and **song_type.js** of types that define user schema. 56 | - **GraphQL Queries** : Inside schema/root_query_type.js file write a very simple GraphQl query and mongoose query used inside to retrieve albums/songs list of data from mongodb database. 57 | - **GraphQL Mutation**: Inside **mutations.js** file we create 4 methods addAlbum, addSong, likeSong and deleteAlbum. add methods creates new album/songs, like method updates the number of likes, delete method deletes the record. 58 | 59 | #### Client directory 60 | [http://localhost:4000](http://localhost:4000) 61 | 62 | **index.js** - Here we create a Apollo Client to establish connection to our GraphQL Server API, And Routing with React 63 | 64 | **queries** - Here we define Apollo GraphQL queries by using `graphql-tag`. Graphql Tag is a JavaScript template literal tag that parses GraphQL queries. 65 | 66 | **components** - Component folder consist of files responsible for creating albums/songs, likes and deleting the album. we used `react-apollo` to connect our components and pass our **mutations** and **queries** defined using `graphql-tag` 67 | -------------------------------------------------------------------------------- /client/components/AlbumCreate.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { graphql } from 'react-apollo' 4 | import gql from 'graphql-tag' 5 | 6 | //graph 7 | import query from '../queries/fetchAlbumsList' 8 | 9 | class AlbumCreate extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | title: '', 14 | } 15 | } 16 | 17 | handleSubmit = (e) => { 18 | e.preventDefault(); 19 | const {title} = this.state; 20 | const { history } = this.props; 21 | 22 | this.props.mutate({ 23 | variables: { title }, 24 | refetchQueries: [{ query }] 25 | }).then(() => history.push('/')) 26 | } 27 | render() { 28 | return ( 29 |
30 | Back 31 |

Create a new Album!

32 |
33 | 34 | this.setState({title: e.target.value})} 37 | value={this.state.title} 38 | /> 39 |
40 |
41 | ) 42 | } 43 | } 44 | 45 | const mutation = gql` 46 | mutation AddAlbum($title: String){ 47 | addAlbum(title: $title) { 48 | id, 49 | title, 50 | } 51 | } 52 | `; 53 | export default graphql(mutation)(AlbumCreate); 54 | -------------------------------------------------------------------------------- /client/components/AlbumDetail.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { graphql } from 'react-apollo' 3 | import { Link } from 'react-router-dom' 4 | 5 | //graph 6 | import albumQuery from '../queries/fetchAlbum' 7 | import SongCreate from './SongCreate' 8 | import SongList from './SongList' 9 | 10 | class AlbumDetail extends Component { 11 | render() { 12 | const { album } = this.props.data 13 | if(!album) {return null} 14 | 15 | return ( 16 |
17 | Back 18 |

{album.title}

19 | 20 | 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default graphql(albumQuery, { 27 | options: (props) => { return { variables: { id: props.match.params.id }}} 28 | })(AlbumDetail); 29 | -------------------------------------------------------------------------------- /client/components/AlbumsList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import gql from 'graphql-tag' 3 | import { graphql, compose } from 'react-apollo' 4 | import { Link } from 'react-router-dom' 5 | 6 | //graph 7 | import query from '../queries/fetchAlbumsList' 8 | 9 | class AlbumList extends Component { 10 | 11 | deleteAlbum(id) { 12 | this.props.mutate({ 13 | variables: { id }, 14 | refetchQueries: [{ query }] 15 | }).then(() => this.props.data.refetch()) 16 | } 17 | renderAlbums() { 18 | const {albums} = this.props.data; 19 | return albums.map(({id, title}) => ( 20 |
  • 21 | 22 | {title} 23 | 24 | this.deleteAlbum(id)} 27 | > 28 | delete 29 | 30 |
  • 31 | )); 32 | } 33 | render() { 34 | const {loading} = this.props.data; 35 | if(loading) { return
    Loading....
    } 36 | return ( 37 |
    38 |

    Album Store

    39 | 42 | 43 | add 44 | 45 |
    46 | ) 47 | } 48 | } 49 | 50 | const mutation = gql` 51 | mutation DeleteAlbum($id: ID) { 52 | deleteAlbum(id: $id) { 53 | id 54 | } 55 | } 56 | `; 57 | export default compose( 58 | graphql(mutation), 59 | graphql(query))(AlbumList); 60 | -------------------------------------------------------------------------------- /client/components/SongCreate.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { graphql } from 'react-apollo' 3 | import gql from 'graphql-tag' 4 | 5 | class SongCreate extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | content: '' 11 | } 12 | } 13 | 14 | handleSubmit = (e) => { 15 | e.preventDefault(); 16 | const { content } = this.state 17 | const { albumId } = this.props 18 | this.props.mutate({ 19 | variables: { 20 | content, 21 | albumId 22 | } 23 | }).then(() => this.setState({content: ''})) 24 | } 25 | render() { 26 | const { content } = this.state 27 | return ( 28 |
    29 | 30 | this.setState({content: evt.target.value})} 33 | /> 34 |
    35 | ) 36 | } 37 | } 38 | 39 | const mutation = gql` 40 | mutation AddSong($content: String, $albumId: ID) { 41 | addSongToAlbum(content: $content, albumId: $albumId) { 42 | id 43 | songs { 44 | id 45 | content 46 | likes 47 | } 48 | } 49 | } 50 | `; 51 | 52 | export default graphql(mutation)(SongCreate); 53 | -------------------------------------------------------------------------------- /client/components/SongList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { graphql } from 'react-apollo' 3 | import gql from 'graphql-tag' 4 | 5 | class SongList extends Component { 6 | 7 | onLike(id, likes) { 8 | this.props.mutate({ 9 | variables: {id}, 10 | optimisticResponse: { 11 | __typename: 'mutation', 12 | likeSong: { 13 | __typename: 'SongType', 14 | id, 15 | likes: likes + 1 16 | } 17 | } 18 | }) 19 | } 20 | renderSongs() { 21 | const {songs} = this.props; 22 | return songs.map(({id, content, likes}) => ( 23 |
  • 24 | {content} 25 |
    26 | this.onLike(id, likes)}>thumb_up 27 | {likes} 28 |
    29 |
  • 30 | )) 31 | } 32 | render() { 33 | return ( 34 | 37 | ) 38 | } 39 | } 40 | 41 | const mutation = gql` 42 | mutation LikeSong($id: ID) { 43 | likeSong(id: $id) { 44 | id 45 | likes 46 | } 47 | } 48 | `; 49 | export default graphql(mutation)(SongList); 50 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
    7 | 8 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { HashRouter, Route } from 'react-router-dom'; 4 | import { ApolloClient } from "apollo-client"; 5 | import { ApolloProvider } from "react-apollo"; 6 | import { createHttpLink } from "apollo-link-http"; 7 | import { InMemoryCache } from "apollo-cache-inmemory"; 8 | import './style/style.css' 9 | 10 | import AlbumList from './components/AlbumsList' 11 | import AlbumCreate from './components/AlbumCreate' 12 | import AlbumDetail from './components/AlbumDetail' 13 | 14 | const cache = new InMemoryCache({ 15 | dataIdFromObject: object => object.id || null 16 | }); 17 | 18 | const client = new ApolloClient({ 19 | link: createHttpLink({ uri: "/graphql" }), 20 | cache 21 | }); 22 | const Root = () => { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | }; 33 | 34 | ReactDOM.render( 35 | , 36 | document.querySelector('#root') 37 | ); 38 | -------------------------------------------------------------------------------- /client/queries/fetchAlbum.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export default gql` 4 | query AlbumQuery($id: ID!) { 5 | album(id: $id) { 6 | id, 7 | title 8 | songs { 9 | id 10 | content 11 | likes 12 | } 13 | } 14 | } 15 | `; -------------------------------------------------------------------------------- /client/queries/fetchAlbumsList.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export default gql` 4 | { 5 | albums { 6 | id 7 | title 8 | } 9 | } 10 | `; -------------------------------------------------------------------------------- /client/style/style.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1244px; 3 | margin: auto; 4 | } 5 | .headline { 6 | text-align: center 7 | } 8 | .collection-item { 9 | display: flex; 10 | justify-content: space-between; 11 | } 12 | 13 | .material-icons { 14 | cursor: pointer; 15 | margin-right: 5px; 16 | } 17 | 18 | .vote-box { 19 | display: flex; 20 | align-items: center; 21 | } 22 | 23 | label { 24 | color: red 25 | } 26 | 27 | @media only screen and (max-width: 1280px) { 28 | #root { 29 | max-width: 980px; 30 | margin: auto; 31 | } 32 | } 33 | 34 | @media only screen and (max-width: 1024px) { 35 | #root { 36 | max-width: 760px; 37 | margin: auto; 38 | } 39 | } 40 | 41 | @media only screen and (max-width: 767px) { 42 | #root { 43 | max-width: 460px; 44 | margin: auto; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hemanth09/creating-crud-app-with-apollo-graphql-node-mongodb-and-react/e76866dcd024a240e60099a2c4f2c24fed26706f/demo.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const app = require('./server/server'); 2 | 3 | app.listen(4000, () => { 4 | console.log('Listening'); 5 | }); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "creating-crud-app-with-apollo-graphql-node-mongodb-and-react", 3 | "version": "1.0.0", 4 | "description": "Creating a simple CRUD app with NodeJS, MongoDB, GraphQL, React and Apollo", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js --ignore client" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "apollo-cache-inmemory": "^1.5.1", 13 | "apollo-client": "^2.5.1", 14 | "apollo-link-http": "^1.5.14", 15 | "axios": "^0.15.3", 16 | "babel-core": "^6.26.3", 17 | "babel-loader": "^8.0.6", 18 | "babel-preset-env": "^1.7.0", 19 | "babel-preset-react": "^6.24.1", 20 | "body-parser": "^1.16.0", 21 | "connect-mongo": "^1.3.2", 22 | "css-loader": "^0.26.1", 23 | "express": "^4.16.4", 24 | "express-graphql": "^0.8.0", 25 | "express-session": "^1.15.0", 26 | "graphql": "^14.3.0", 27 | "graphql-tag": "^2.10.1", 28 | "html-webpack-plugin": "^2.26.0", 29 | "lodash": "^4.17.4", 30 | "mongoose": "^5.5.7", 31 | "nodemon": "*", 32 | "passport": "^0.3.2", 33 | "passport-local": "^1.0.0", 34 | "react": "^16.8.6", 35 | "react-apollo": "^2.5.5", 36 | "react-dom": "^16.8.6", 37 | "react-router": "^5.0.0", 38 | "react-router-dom": "^5.0.0", 39 | "style-loader": "^0.13.1", 40 | "webpack": "^2.2.0", 41 | "webpack-dev-middleware": "^1.9.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.4.4", 45 | "@babel/plugin-proposal-class-properties": "^7.4.4", 46 | "@babel/preset-env": "^7.4.4", 47 | "@babel/preset-react": "^7.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/models/album.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const AlbumSchema = new Schema({ 5 | title: { type: String }, 6 | user: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'user' 9 | }, 10 | songs: [{ 11 | type: Schema.Types.ObjectId, 12 | ref: 'song' 13 | }] 14 | }); 15 | 16 | AlbumSchema.statics.addSong = function(id, content) { 17 | const Song = mongoose.model('song'); 18 | 19 | return this.findById(id) 20 | .then(album => { 21 | const song = new Song({ content, album }) 22 | album.songs.push(song) 23 | return Promise.all([song.save(), album.save()]) 24 | .then(([song, album]) => album); 25 | }); 26 | } 27 | 28 | AlbumSchema.statics.findSongs = function(id) { 29 | return this.findById(id) 30 | .populate('songs') 31 | .then(album => album.songs); 32 | } 33 | 34 | mongoose.model('album', AlbumSchema); 35 | -------------------------------------------------------------------------------- /server/models/index.js: -------------------------------------------------------------------------------- 1 | require('./album'); 2 | require('./song'); 3 | -------------------------------------------------------------------------------- /server/models/song.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const SongSchema = new Schema({ 5 | album: { 6 | type: Schema.Types.ObjectId, 7 | ref: 'album' 8 | }, 9 | likes: { type: Number, default: 0 }, 10 | content: { type: String } 11 | }); 12 | 13 | SongSchema.statics.like = function(id) { 14 | const Song = mongoose.model('song'); 15 | 16 | return Song.findById(id) 17 | .then(song => { 18 | ++song.likes; 19 | return song.save(); 20 | }) 21 | } 22 | 23 | mongoose.model('song', SongSchema); 24 | -------------------------------------------------------------------------------- /server/schema/album_type.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const graphql = require('graphql'); 3 | const { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLList } = graphql; 4 | const SongType = require('./song_type'); 5 | const Album = mongoose.model('album'); 6 | 7 | const AlbumType = new GraphQLObjectType({ 8 | name: 'AlbumType', 9 | fields: () => ({ 10 | id: { type: GraphQLID }, 11 | title: { type: GraphQLString }, 12 | songs: { 13 | type: new GraphQLList(SongType), 14 | resolve(parentValue) { 15 | return Album.findSongs(parentValue.id); 16 | } 17 | } 18 | }) 19 | }); 20 | 21 | module.exports = AlbumType; 22 | -------------------------------------------------------------------------------- /server/schema/mutations.js: -------------------------------------------------------------------------------- 1 | const graphql = require('graphql'); 2 | const { GraphQLObjectType, GraphQLString, GraphQLID } = graphql; 3 | const mongoose = require('mongoose'); 4 | const Album = mongoose.model('album'); 5 | const Song = mongoose.model('song'); 6 | const AlbumType = require('./album_type'); 7 | const SongType = require('./song_type'); 8 | 9 | const mutation = new GraphQLObjectType({ 10 | name: 'Mutation', 11 | fields: { 12 | addAlbum: { 13 | type: AlbumType, 14 | args: { 15 | title: { type: GraphQLString } 16 | }, 17 | resolve(parentValue, { title }) { 18 | return (new Album({ title })).save() 19 | } 20 | }, 21 | addSongToAlbum: { 22 | type: AlbumType, 23 | args: { 24 | content: { type: GraphQLString }, 25 | albumId: { type: GraphQLID } 26 | }, 27 | resolve(parentValue, { content, albumId }) { 28 | return Album.addSong(albumId, content); 29 | } 30 | }, 31 | likeSong: { 32 | type: SongType, 33 | args: { id: { type: GraphQLID } }, 34 | resolve(parentValue, { id }) { 35 | return Song.like(id); 36 | } 37 | }, 38 | deleteAlbum: { 39 | type: AlbumType, 40 | args: { id: { type: GraphQLID } }, 41 | resolve(parentValue, { id }) { 42 | return Album.remove({ _id: id }); 43 | } 44 | } 45 | } 46 | }); 47 | 48 | module.exports = mutation; 49 | -------------------------------------------------------------------------------- /server/schema/root_query_type.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const graphql = require('graphql'); 3 | const { GraphQLObjectType, GraphQLList, GraphQLID, GraphQLNonNull } = graphql; 4 | const AlbumType = require('./album_type'); 5 | const SongType = require('./song_type'); 6 | const Song = mongoose.model('song'); 7 | const Album = mongoose.model('album'); 8 | 9 | const RootQuery = new GraphQLObjectType({ 10 | name: 'RootQueryType', 11 | fields: () => ({ 12 | albums: { 13 | type: new GraphQLList(AlbumType), 14 | resolve() { 15 | return Album.find({}); 16 | } 17 | }, 18 | album: { 19 | type: AlbumType, 20 | args: { id: { type: new GraphQLNonNull(GraphQLID) } }, 21 | resolve(parentValue, { id }) { 22 | return Album.findById(id); 23 | } 24 | }, 25 | song: { 26 | type: SongType, 27 | args: { id: { type: new GraphQLNonNull(GraphQLID) } }, 28 | resolve(parnetValue, { id }) { 29 | return Song.findById(id); 30 | } 31 | } 32 | }) 33 | }); 34 | 35 | module.exports = RootQuery; 36 | -------------------------------------------------------------------------------- /server/schema/schema.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const graphql = require('graphql'); 3 | const { GraphQLSchema } = graphql; 4 | 5 | const RootQueryType = require('./root_query_type'); 6 | const mutations = require('./mutations'); 7 | 8 | module.exports = new GraphQLSchema({ 9 | query: RootQueryType, 10 | mutation: mutations 11 | }); 12 | -------------------------------------------------------------------------------- /server/schema/song_type.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const graphql = require('graphql'); 3 | const { 4 | GraphQLObjectType, 5 | GraphQLList, 6 | GraphQLID, 7 | GraphQLInt, 8 | GraphQLString 9 | } = graphql; 10 | const Song = mongoose.model('song'); 11 | 12 | const SongType = new GraphQLObjectType({ 13 | name: 'SongType', 14 | fields: () => ({ 15 | id: { type: GraphQLID }, 16 | likes: { type: GraphQLInt }, 17 | content: { type: GraphQLString }, 18 | song: { 19 | type: require('./album_type'), 20 | resolve(parentValue) { 21 | return Song.findById(parentValue).populate('song') 22 | .then(song => { 23 | console.log(song) 24 | return song.song 25 | }); 26 | } 27 | } 28 | }) 29 | }); 30 | 31 | module.exports = SongType; 32 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const models = require('./models'); 3 | const expressGraphQL = require('express-graphql'); 4 | const mongoose = require('mongoose'); 5 | const bodyParser = require('body-parser'); 6 | const schema = require('./schema/schema'); 7 | 8 | const app = express(); 9 | 10 | // Replace with your mongoLab URI 11 | const MONGO_URI = ''; 12 | if (!MONGO_URI) { 13 | throw new Error('You must provide a MongoLab URI'); 14 | } 15 | 16 | mongoose.Promise = global.Promise; 17 | mongoose.connect(MONGO_URI); 18 | mongoose.connection 19 | .once('open', () => console.log('Connected to MongoLab instance.')) 20 | .on('error', error => console.log('Error connecting to MongoLab:', error)); 21 | 22 | app.use(bodyParser.json()); 23 | app.use('/graphql', expressGraphQL({ 24 | schema, 25 | graphiql: true 26 | })); 27 | 28 | const webpackMiddleware = require('webpack-dev-middleware'); 29 | const webpack = require('webpack'); 30 | const webpackConfig = require('../webpack.config.js'); 31 | app.use(webpackMiddleware(webpack(webpackConfig))); 32 | 33 | module.exports = app; 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './client/index.js', 6 | output: { 7 | path: '/', 8 | filename: 'bundle.js' 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | use: 'babel-loader', 14 | test: /\.js$/, 15 | exclude: /node_modules/ 16 | }, 17 | { 18 | use: ['style-loader', 'css-loader'], 19 | test: /\.css$/ 20 | } 21 | ] 22 | }, 23 | plugins: [ 24 | new HtmlWebpackPlugin({ 25 | template: 'client/index.html' 26 | }) 27 | ] 28 | }; 29 | --------------------------------------------------------------------------------