Sample React-Express-MongoDB (#59)

Signed-off-by: Afzal <sah.afzal@gmail.com>
This commit is contained in:
Syed Afzal 2020-05-12 00:40:39 +05:00 committed by GitHub
parent 3599a2e685
commit 2f750eb4f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 18779 additions and 0 deletions

View File

@ -0,0 +1 @@
server/node_modules

4
react-express-mongodb/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
frontend/node_modules/
server/node_modules/
.idea/
data

View File

@ -0,0 +1,134 @@
## Compose sample application
### React application with a NodeJS backend and a MongoDB database
Project structure:
```
.
├── backend
│ ├── Dockerfile
│ ...
├── docker-compose.yaml
├── frontend
│ ├── ...
│ └── Dockerfile
└── README.md
```
[_docker-compose.yaml_](docker-compose.yaml)
```
services:
frontend:
build:
context: frontend
...
ports:
- 5000:5000
...
server:
container_name: server
restart: always
build:
context: server
args:
NODE_PORT: 3000
ports:
- 3000:3000
...
depends_on:
- mongo
mongo:
container_name: mongo
restart: always
...
```
The compose file defines an application with three services `frontend`, `backend` and `db`.
When deploying the application, docker-compose maps port 5000 of the frontend service container to port 5000 of the host as specified in the file.
Make sure port 5000 on the host is not already being in use.
## Deploy with docker-compose
```
$ docker-compose up -d
Creating network "react-express-mongodb_default" with the default driver
Building frontend
Step 1/9 : FROM node:13.13.0-stretch-slim
---> aa6432763c11
...
Successfully tagged react-express-mongodb_app:latest
WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating frontend ... done
Creating mongo ... done
Creating app ... done
```
## Expected result
Listing containers must show containers running and the port mapping as below:
```
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
06e606d69a0e react-express-mongodb_server "docker-entrypoint.s…" 23 minutes ago Up 23 minutes 0.0.0.0:3000->3000/tcp server
ff56585e1db4 react-express-mongodb_frontend "docker-entrypoint.s…" 23 minutes ago Up 23 minutes 0.0.0.0:5000->5000/tcp frontend
a1f321f06490 mongo:4.2.0 "docker-entrypoint.s…" 23 minutes ago Up 23 minutes 0.0.0.0:27017->27017/tcp mongo
```
After the application starts, navigate to `http://localhost:5000` in your web browser.
![page](./output.png)
Stop and remove the containers
```
$ docker-compose down
Stopping server ... done
Stopping frontend ... done
Stopping mongo ... done
Removing server ... done
Removing frontend ... done
Removing mongo ... done
```
##### Explanation of `docker-compose`
__Version__
The first line defines the version of a file. It sounds confusing :confused:. What is meant by version of file ??
:pill: The Compose file is a YAML file defining services, networks, and volumes for a Docker application. So it is only a version of describing compose.yaml file. There are several versions of the Compose file format 1, 2, 2.x, and 3.x.
__Services__
Our main goal to create a containers, it starts from here. As you can see there are three services(Docker images):
- First is __frontend__
- Second is __server__ which is __backend - Express(NodeJS)__. I used a name server here, it's totally on you to name it __backend__.
- Third is __mongo__ which is db __MongoDB__.
##### Service app (backend - NodeJS)
We make image of app from our `DockeFile`, explanation below.
__Explanation of service server__
- Defining a **nodejs** service as __server__.
- We named our **node server** container service as **server**. Assigning a name to the containers makes it easier to read when there are lot of containers on a machine, it can aslo avoid randomly generated container names. (Although in this case, __container_name__ is also __server__, this is merely personal preference, the name of the service and container do not have to be the same.)
- Docker container starts automatically if its fails.
- Building the __server__ image using the Dockerfile from the current directory and passing an argument to the
backend(server) `DockerFile`.
- Mapping the host port to the container port.
##### Service mongo
We add another service called **mongo** but this time instead of building it from `DockerFile` we write all the instruction here directly. We simply pull down the standard __mongo image__ from the [DockerHub](https://hub.docker.com/) registry as we have done it for Node image.
__Explanation of service mongo__
- Defining a **mongodb** service as __mongo__.
- Pulling the mongo 4.2.0 image image again from [DockerHub](https://hub.docker.com/).
- Mount our current db directory to container.
- For persistent storage, we mount the host directory ( just like I did it in **Node** image inside `DockerFile` to reflect the changes) `/data` ( you need to create a directory in root of your project in order to save changes to locally as well) to the container directory `/data/db`, which was identified as a potential mount point in the `mongo Dockerfile` we saw earlier.
- Mounting volumes gives us persistent storage so when starting a new container, Docker Compose will use the volume of any previous containers and copy it to the new container, ensuring that no data is lost.
- Finally, we link/depends_on the app container to the mongo container so that the mongo service is reachable from the app service.
- In last mapping the host port to the container port.
:key: `If you wish to check your DB changes on your local machine as well. You should have installed MongoDB locally, otherwise you can't access your mongodb service of container from host machine.`
:white_check_mark: You should check your __mongo__ version is same as used in image. You can see the version of __mongo__ image in `docker-compose `file, I used __image: mongo:4.2.0__. If your mongo db version on your machine is not same then furst you have to updated your local __mongo__ version in order to works correctly.

View File

@ -0,0 +1,37 @@
version: "3.7"
services:
frontend:
build:
context: frontend
args:
FRONT_END_PORT: 5000
ports:
- 5000:5000
stdin_open: true
volumes:
- ./frontend:/usr/src/app
- /usr/src/app/node_modules
container_name: frontend
restart: always
server:
container_name: server
restart: always
build:
context: server
args:
NODE_PORT: 3000
ports:
- 3000:3000
volumes:
- ./server:/usr/src/app
- /usr/src/app/node_modules
depends_on:
- mongo
mongo:
container_name: mongo
restart: always
image: mongo:4.2.0
volumes:
- ./data:/data/db
ports:
- 27017:27017

View File

@ -0,0 +1,3 @@
node_modules
npm-debug.log
server/logs/

View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.idea
# 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*

View File

@ -0,0 +1,30 @@
# Create image based on the official Node image from dockerhub
FROM node:13.13.0-stretch
#Argument that is passed from docer-compose.yaml file
ARG FRONT_END_PORT
# Create app directory
WORKDIR /usr/src/app
#Echo the argument to check passed argument loaded here correctly
RUN echo "Argument port is : $FRONT_END_PORT"
# Copy dependency definitions
COPY package.json /usr/src/app
COPY package-lock.json /usr/src/app
# Install dependecies
#RUN npm set progress=false \
# && npm config set depth 0 \
# && npm i install
RUN npm ci
# Get all the code needed to run the app
COPY . /usr/src/app
# Expose the port the app runs in
EXPOSE ${FRONT_END_PORT}
# Serve the app
CMD ["npm", "start"]

View File

@ -0,0 +1,27 @@
#### Snippet of frontend(ReactJS)`DockerFile`
You will find this `DockerFile` inside **frontend** directory.
```bash
# Create image based on the official Node image from dockerhub
FROM node:10
#Argument that is passed from docer-compose.yaml file
ARG FRONT_END_PORT
# Create app directory
WORKDIR /usr/src/app
#Echo the argument to check passed argument loaded here correctly
RUN echo "Argument port is : $FRONT_END_PORT"
# Copy dependency definitions
COPY package.json /usr/src/app
# Install dependecies
RUN npm install
# Get all the code needed to run the app
COPY . /usr/src/app
# Expose the port the app runs in
EXPOSE ${FRONT_END_PORT}
# Serve the app
CMD ["npm", "start"]
```
##### Explanation of frontend(ReactJS) `DockerFile`
Frontend `DockerFile` is almost the same as Backend `DockerFile`.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.19.0",
"bootstrap": "^4.3.1",
"node-sass": "^4.14.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
"optionalDependencies": {
"fsevents": "^2.1.2"
},
"scripts": {
"start": "PORT=5000 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,56 @@
import React from 'react';
import {request} from './utilities/httpRequestHandler'
import './App.scss';
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";
export default class App extends React.Component{
constructor(props){
super(props);
this.state = {
todos: []
}
}
componentDidMount() {
request('get','/api')
.then((response) => {
this.setState({
todos: response.data.data
})
}).catch((e) => console.log('Error : ', e))
}
_handleAddTodo = (value) => {
request('post', '/api/todos', {text:value})
.then((response) => {
let todosCopy = this.state.todos;
todosCopy.unshift({text:value});
this.setState({
todos : todosCopy
});
this.refs.todo.value = ""
}).catch((e) => console.log('Error : ', e));
};
render() {
return (
<div className="App container">
<div className="container-fluid">
<div className="row">
<div className="col-xs-12 col-sm-8 col-md-8 offset-md-2">
<h1>Todos</h1>
<div className="todo-app">
<AddTodo handleAddTodo={(value) => {this._handleAddTodo(value)}}/>
<TodoList todos={this.state.todos}/>
</div>
</div>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,24 @@
.App {
text-align: center;
}
.todo-app {
background-color: #efefef;
padding: 1.2em;
.new-todo{
display: flex;
justify-content: space-between;
align-items: center;
input{
width: 80% !important;
}
}
}
//.list-group-item{
// &.active:hover{
//
// }
// &active:hover{
// background-color: #d3d3d3;
// }
//}

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@ -0,0 +1,22 @@
import React from 'react';
export default class AddTodo extends React.Component {
_onAddTodo = () => {
if(this.refs.todo.value.length > 0) {
this.props.handleAddTodo(this.refs.todo.value);
this.refs.todo.value = '';
}
};
render() {
return (
<div className="new-todo form-group">
<input type="text" className="form-control" ref="todo"/>
<button className="btn btn-primary" onClick={this._onAddTodo}>
Add Todo
</button>
</div>
)
}
}

View File

@ -0,0 +1,48 @@
import React from 'react';
export default class TodoList extends React.Component {
constructor(props){
super(props);
this.state = {
activeIndex:0,
}
}
_handleActive(index) {
this.setState({
activeIndex: index
})
}
_renderTodos(todos) {
return (
<ul className="list-group">
{
todos.map((todo, i) => {
return (<li className={'list-group-item cursor-pointer ' + (i===this.state.activeIndex ? 'active' : '')}
key={i}
onClick={() => {this._handleActive(i)}}
>
{todo.text}
</li>)
})
}
</ul>
)
}
render() {
let { todos } = this.props;
return (
todos.length > 0 ?
this._renderTodos(todos)
:
<div className="alert alert-primary" role="alert">
No Todos to display
</div>
)
}
}

View File

@ -0,0 +1,5 @@
const config = {
API_BASE_URL: 'http://localhost:3000',
};
export default config;

View File

@ -0,0 +1,8 @@
// Override default variables before the import
$body-bg: #fff;
// Import Bootstrap and its default variables
@import '~bootstrap/scss/bootstrap.scss';
.cursor-pointer {
cursor: pointer;
}

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

View File

@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import './custom.scss';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@ -0,0 +1,22 @@
import axios from 'axios';
import config from '../config/constants';
export async function request (method, uri, data, headers = null, params = null) {
let url = (config.API_BASE_URL + uri);
let query = {
method: method,
url: url
};
if (headers !== null)
query.headers = headers;
if (params !== null)
query.params = params;
if (method === 'post' || method === 'put' || method === 'delete' || method === 'patch')
query.data = data;
try {
return await axios(query);
} catch (e) {
throw e;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,27 @@
FROM node:13.13.0-stretch-slim
#Argument that is passed from docer-compose.yaml file
ARG NODE_PORT
#Echo the argument to check passed argument loaded here correctly
RUN echo "Argument port is : $NODE_PORT"
# Create app directory
WORKDIR /usr/src/app
#COPY . .
COPY . .
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
RUN npm install
#In my case my app binds to port NODE_PORT so you'll use the EXPOSE instruction to have it mapped by the docker daemon:
EXPOSE ${NODE_PORT}
CMD npm run dev

View File

@ -0,0 +1,41 @@
#### Snippet of backend(Node.js)`DockerFile`
You will find this `DockerFile` file in the root directory of the project.
```bash
FROM node:13.13.0-stretch-slim
#Argument that is passed from docer-compose.yaml file
ARG NODE_PORT
#Echo the argument to check passed argument loaded here correctly
RUN echo "Argument port is : $NODE_PORT"
# Create app directory
WORKDIR /usr/src/app
#COPY . .
COPY . .
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
RUN npm install
#In my case my app binds to port NODE_PORT so you'll use the EXPOSE instruction to have it mapped by the docker daemon:
EXPOSE ${NODE_PORT}
CMD npm run dev
```
##### Explanation of backend(Node.js) `DockerFile`
- The first line tells Docker to use another Node image from the [DockerHub](https://hub.docker.com/). Were using the official Docker image for Node.js and its version 10 image.
- On second line we declare argument `NODE_PORT` which we will pass it from `docker-compose`.
- On third line we log to check argument is successfully read
- On fourth line we sets a working directory from where the app code will live inside the Docker container.
- On fifth line, we are copying/bundling our code working directory into container working directory on line three.
- On line seven, we run npm install for dependencies in container on line four.
- On Line eight, we setup the port, that Docker will expose when the container is running. In our case it is the port which we define inside `.env` file, read it from `docker-compose` then passed as a argument to the (backend)`DockerFile`.
- And in last, we tell docker to execute our app inside the container by using node to run `npm run dev. It is the command which I registered in __package.json__ in script section.
###### :clipboard: `Note: For development purpose I used __nodemon__ , If you need to deploy at production you should change CMD from __npm run dev__ to __npm start__.`

View File

@ -0,0 +1,12 @@
var env = process.env.NODE_ENV || 'development';
if(env === 'development' || env === 'test'){
const config = require('./config.json')
let envConfig = config[env];
console.log(envConfig);
Object.keys(envConfig).forEach((key) => {
process.env[key] = envConfig[key]
})
}

View File

@ -0,0 +1,10 @@
{
"test":{
"PORT": 3000,
"MONGODB_URI": "mongodb://mongo:27017/TodoAppTest"
},
"development":{
"PORT": 3000,
"MONGODB_URI": "mongodb://mongo:27017/TodoApp"
}
}

View File

@ -0,0 +1,62 @@
module.exports = {
AUTHENTICATION_FAILED: {
code: 400,
message: 'Authentication failed. Please login with valid credentials.',
success: false,
},
SUCCESSFUL_LOGIN: {
code: 200,
message: 'Successfully logged in',
success: true,
},
INTERNAL_SERVER_ERROR: {
code: 500,
message: 'Something unexpected happened',
success: false,
},
UNAUTHORIZED: {
code: 401,
message: 'You session is expired. Please login again',
success: false,
},
SUCCESSFUL_DELETE: {
code: 200,
message: 'Successfully deleted',
success: true,
},
SUCCESSFUL_UPDATE: {
code: 200,
message: 'Updated successfully',
success: true,
},
SUCCESSFUL: {
code: 200,
success: true,
message: 'Successfully completed',
},
NOT_FOUND: {
code: 404,
success: true,
message: 'Requested API not found',
},
ALREADY_EXIST: {
code: 200,
success: true,
message: 'Already exists',
},
FORBIDDEN: {
code: 403,
message: 'You are not authorized to complete this action',
success: false,
},
BAD_REQUEST: {
code: 400,
message: 'Bad request. Please try again with valid parameters',
success: false,
},
IN_COMPLETE_REQUEST: {
code: 422,
message: 'Required parameter missing',
success: false,
},
};

View File

@ -0,0 +1,30 @@
/**
* Created by Syed Afzal
*/
const mongoose = require('mongoose');
const {log} = require('../utils/helpers/logger');
exports.connect = (app) => {
const options = {
useNewUrlParser: true,
autoIndex: false, // Don't build indexes
reconnectTries: 30, // Retry up to 30 times
reconnectInterval: 500, // Reconnect every 500ms
poolSize: 10, // Maintain up to 10 socket connections
// If not connected, return errors immediately rather than waiting for reconnect
bufferMaxEntries: 0
}
const connectWithRetry = () => {
mongoose.Promise = global.Promise;
console.log('MongoDB connection with retry')
mongoose.connect(process.env.MONGODB_URI, options).then(()=>{
console.log('MongoDB is connected');
app.emit('ready');
}).catch(err=>{
console.log('MongoDB connection unsuccessful, retry after 2 seconds.')
setTimeout(connectWithRetry, 2000)
})
}
connectWithRetry();
};

Binary file not shown.

View File

@ -0,0 +1,14 @@
/**
* Created by Syed Afzal
*/
const mongoose = require('mongoose');
var Todo = mongoose.model('Todo', {
text : {
type: String,
trim: true,
required: true
}
});
module.exports = {Todo};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
{
"name": "docker_node_mongo_starter",
"version": "1.0.0",
"description": "docker starter with node js and mongodb services",
"main": "index.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"author": "Syed Afzal",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.2",
"cookie-parser": "^1.4.4",
"cors": "^2.8.4",
"express": "^4.17.1",
"lodash": "^4.17.13",
"mongodb": "^3.0.7",
"mongoose": "^5.0.15",
"simple-node-logger": "^18.12.23",
"validator": "^10.1.0"
},
"devDependencies": {
"nodemon": "^2.0.3"
}
}

View File

@ -0,0 +1,38 @@
const express = require('express');
const serverResponses = require('../utils/helpers/server.responses');
const messages = require('../config/messages');
var {Todo} = require('../models/todos/todo.model');
const routes = (app) => {
const router = express.Router();
router.post('/todos', (req,res)=>{
var todo = new Todo({
text: req.body.text
});
todo.save()
.then((result)=>{
serverResponses.sendSuccess(res,messages.SUCCESSFUL, result);
})
.catch((e) => {
serverResponses.sendError(res,messages.BAD_REQUEST,e)
})
});
router.get('/', (req,res) => {
Todo.find({}, {__v:0})
.then((todos)=>{
serverResponses.sendSuccess(res,messages.SUCCESSFUL, todos);
})
.catch((e) => {
serverResponses.sendError(res,messages.BAD_REQUEST,e)
})
});
//it's a prefix before api it is useful when you have many modules and you want to
//differentiate b/w each module you can use this technique
app.use('/api', router);
};
module.exports = routes;

35
react-express-mongodb/server/server.js vendored Normal file
View File

@ -0,0 +1,35 @@
/**
* Created by Syed Afzal
*/
require('./config/config');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const cors = require('cors');
const db = require('./db');
const app = express();
//connection from db here
db.connect(app);
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// adding routes
require('./routes')(app);
app.on('ready', () => {
app.listen(3000, () => {
console.log("Server is up on port", 3000)
});
})
module.exports = app;

View File

@ -0,0 +1,9 @@
const path = require('path');
const filename = path.join(__dirname, '../../logs/project.log');
//you can change format according to you
const log = require('simple-node-logger').createSimpleLogger( {
logFilePath:filename,
timestampFormat:'YYYY-MM-DD HH:mm:ss'}
);
module.exports = {log};

View File

@ -0,0 +1,21 @@
const serverResponse = {
sendSuccess: (res, message, data = null) => {
const responseMessage = {
code: message.code ? message.code : 500,
success: message.success,
message: message.message,
};
if (data) { responseMessage.data = data; }
return res.status(message.code).json(responseMessage);
},
sendError: (res, error) => {
const responseMessage = {
code: error.code ? error.code : 500,
success: false,
message: error.message,
};
return res.status(error.code ? error.code : 500).json(responseMessage);
},
};
module.exports = serverResponse;