├── .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 - 
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 |
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 |
40 | {this.renderAlbums()}
41 |
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 |
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 |
35 | {this.renderSongs()}
36 |
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 |
--------------------------------------------------------------------------------