├── rmbyext
└── .gitkeep
├── .github
└── FUNDING.yml
├── hello-dock
├── .dockerignore
├── public
│ └── favicon.ico
├── src
│ ├── main.js
│ ├── assets
│ │ └── docker-handbook-github.webp
│ ├── index.css
│ ├── App.vue
│ └── components
│ │ └── HelloDock.vue
├── package.json
└── index.html
├── notes-api
├── api
│ ├── .dockerignore
│ ├── nodemon.json
│ ├── models
│ │ ├── index.js
│ │ └── Note.js
│ ├── jest.config.js
│ ├── .env.example
│ ├── services
│ │ ├── index.js
│ │ ├── knex.js
│ │ └── notes.js
│ ├── .prettierrc.js
│ ├── .eslintrc.js
│ ├── api
│ │ ├── index.js
│ │ └── routes
│ │ │ └── notes.js
│ ├── migrations
│ │ └── 20200619195837_create_notes_table.js
│ ├── tests
│ │ └── e2e
│ │ │ └── api
│ │ │ ├── index.test.js
│ │ │ └── routes
│ │ │ └── notes.test.js
│ ├── bin
│ │ └── www
│ ├── knexfile.js
│ ├── package.json
│ └── app.js
├── Makefile
├── shutdown.sh
├── boot.sh
├── destroy.sh
└── build.sh
├── fullstack-notes-application
├── client
│ ├── .browserslistrc
│ ├── .dockerignore
│ ├── jest.config.js
│ ├── babel.config.js
│ ├── src
│ │ ├── libs
│ │ │ └── axios.js
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── main.js
│ │ ├── components
│ │ │ └── Note.vue
│ │ ├── App.vue
│ │ ├── router
│ │ │ └── index.js
│ │ ├── views
│ │ │ ├── Home.vue
│ │ │ └── Create.vue
│ │ └── store
│ │ │ └── index.js
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── .editorconfig
│ ├── Dockerfile.dev
│ ├── webpack.config.js
│ ├── Dockerfile
│ ├── vue.config.js
│ ├── tests
│ │ └── unit
│ │ │ └── note.spec.js
│ ├── .eslintrc.js
│ └── package.json
├── api
│ ├── .dockerignore
│ ├── .env.example
│ ├── nodemon.json
│ ├── models
│ │ ├── index.js
│ │ └── Note.js
│ ├── jest.config.js
│ ├── services
│ │ ├── index.js
│ │ ├── knex.js
│ │ └── notes.js
│ ├── .prettierrc.js
│ ├── .eslintrc.js
│ ├── api
│ │ ├── index.js
│ │ └── routes
│ │ │ └── notes.js
│ ├── migrations
│ │ └── 20200619195837_create_notes_table.js
│ ├── tests
│ │ └── e2e
│ │ │ └── api
│ │ │ ├── index.test.js
│ │ │ └── routes
│ │ │ └── notes.test.js
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── bin
│ │ └── www
│ ├── knexfile.js
│ ├── package.json
│ └── app.js
├── nginx
│ ├── Dockerfile.dev
│ ├── Dockerfile
│ ├── production.conf
│ └── development.conf
├── Makefile
├── shutdown.sh
├── boot.sh
├── destroy.sh
└── build.sh
├── docker-handbook-github.png
├── custom-nginx
└── nginx-1.19.2.tar.gz
├── LICENSE
├── README.md
└── .gitignore
/rmbyext/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: fhsinchy
2 | custom: buymeacoffee.com/farhanhasin
3 |
--------------------------------------------------------------------------------
/hello-dock/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *Dockerfile*
3 | *docker-compose*
4 | node_modules
--------------------------------------------------------------------------------
/notes-api/api/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *Dockerfile*
3 | *docker-compose*
4 | node_modules
--------------------------------------------------------------------------------
/fullstack-notes-application/client/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/notes-api/api/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "verbose": true,
3 | "ignore": ["*.test.js"]
4 | }
--------------------------------------------------------------------------------
/notes-api/api/models/index.js:
--------------------------------------------------------------------------------
1 | const Note = require('./Note');
2 |
3 | module.exports = { Note };
4 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *Dockerfile*
3 | *docker-compose*
4 | node_modules
--------------------------------------------------------------------------------
/fullstack-notes-application/api/.env.example:
--------------------------------------------------------------------------------
1 | DB_HOST=notes-db
2 | DB_DATABASE=notesdb
3 | DB_PASSWORD=secret
--------------------------------------------------------------------------------
/fullstack-notes-application/api/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "verbose": true,
3 | "ignore": ["*.test.js"]
4 | }
--------------------------------------------------------------------------------
/fullstack-notes-application/client/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *Dockerfile*
3 | *docker-compose*
4 | node_modules
--------------------------------------------------------------------------------
/notes-api/api/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | testEnvironment: 'node',
4 | };
5 |
--------------------------------------------------------------------------------
/docker-handbook-github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/objects/docker-handbook-projects/master/docker-handbook-github.png
--------------------------------------------------------------------------------
/fullstack-notes-application/api/models/index.js:
--------------------------------------------------------------------------------
1 | const Note = require('./Note');
2 |
3 | module.exports = { Note };
4 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest',
3 | };
4 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | testEnvironment: 'node',
4 | };
5 |
--------------------------------------------------------------------------------
/hello-dock/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/objects/docker-handbook-projects/master/hello-dock/public/favicon.ico
--------------------------------------------------------------------------------
/notes-api/api/.env.example:
--------------------------------------------------------------------------------
1 | DB_HOST=notes-db
2 | DB_PORT=5432
3 | DB_USER=postgres
4 | DB_DATABASE=notesdb
5 | DB_PASSWORD=secret
--------------------------------------------------------------------------------
/custom-nginx/nginx-1.19.2.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/objects/docker-handbook-projects/master/custom-nginx/nginx-1.19.2.tar.gz
--------------------------------------------------------------------------------
/fullstack-notes-application/nginx/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM nginx:stable-alpine
2 |
3 | COPY ./development.conf /etc/nginx/conf.d/default.conf
--------------------------------------------------------------------------------
/fullstack-notes-application/client/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset',
4 | ],
5 | };
6 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/libs/axios.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export default axios.create({
4 | baseURL: '/api',
5 | });
6 |
--------------------------------------------------------------------------------
/fullstack-notes-application/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:stable-alpine
2 |
3 | EXPOSE 80
4 |
5 | COPY ./production.conf /etc/nginx/conf.d/default.conf
--------------------------------------------------------------------------------
/hello-dock/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import './index.css'
4 |
5 | createApp(App).mount('#app')
6 |
--------------------------------------------------------------------------------
/notes-api/api/services/index.js:
--------------------------------------------------------------------------------
1 | const Knex = require('./knex');
2 | const NotesService = require('./notes');
3 |
4 | module.exports = { Knex, NotesService };
5 |
--------------------------------------------------------------------------------
/hello-dock/src/assets/docker-handbook-github.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/objects/docker-handbook-projects/master/hello-dock/src/assets/docker-handbook-github.webp
--------------------------------------------------------------------------------
/fullstack-notes-application/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/objects/docker-handbook-projects/master/fullstack-notes-application/client/public/favicon.ico
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/objects/docker-handbook-projects/master/fullstack-notes-application/client/src/assets/logo.png
--------------------------------------------------------------------------------
/notes-api/api/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: "all",
4 | singleQuote: true,
5 | printWidth: 100,
6 | tabWidth: 2
7 | };
--------------------------------------------------------------------------------
/notes-api/api/services/knex.js:
--------------------------------------------------------------------------------
1 | const knex = require('knex');
2 |
3 | const config = require('../knexfile');
4 |
5 | module.exports = knex(config[process.env.NODE_ENV]);
6 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/services/index.js:
--------------------------------------------------------------------------------
1 | const Knex = require('./knex');
2 | const NotesService = require('./notes');
3 |
4 | module.exports = { Knex, NotesService };
5 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/services/knex.js:
--------------------------------------------------------------------------------
1 | const knex = require('knex');
2 |
3 | const config = require('../knexfile');
4 |
5 | module.exports = knex(config[process.env.NODE_ENV]);
6 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: "all",
4 | singleQuote: true,
5 | printWidth: 100,
6 | tabWidth: 2
7 | };
--------------------------------------------------------------------------------
/notes-api/api/models/Note.js:
--------------------------------------------------------------------------------
1 | const { Model } = require('objection');
2 |
3 | class Note extends Model {
4 | static get tableName() {
5 | return 'notes';
6 | }
7 | }
8 |
9 | module.exports = Note;
10 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/models/Note.js:
--------------------------------------------------------------------------------
1 | const { Model } = require('objection');
2 |
3 | class Note extends Model {
4 | static get tableName() {
5 | return 'notes';
6 | }
7 | }
8 |
9 | module.exports = Note;
10 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | end_of_line = lf
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | max_line_length = 100
8 |
--------------------------------------------------------------------------------
/hello-dock/src/index.css:
--------------------------------------------------------------------------------
1 | #app {
2 | font-family: Avenir, Helvetica, Arial, sans-serif;
3 | -webkit-font-smoothing: antialiased;
4 | -moz-osx-font-smoothing: grayscale;
5 | text-align: center;
6 | color: #2c3e50;
7 | margin-top: 60px;
8 | }
9 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine
2 |
3 | USER node
4 |
5 | RUN mkdir -p /home/node/app
6 |
7 | WORKDIR /home/node/app
8 |
9 | COPY ./package.json .
10 | RUN npm install
11 |
12 | CMD [ "npm", "run", "serve" ]
--------------------------------------------------------------------------------
/fullstack-notes-application/client/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.css$/i,
6 | use: [
7 | 'css-loader',
8 | ],
9 | },
10 | ],
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/notes-api/api/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["prettier"],
3 | extends: [
4 | 'airbnb-base',
5 | 'plugin:prettier/recommended'
6 | ],
7 | env: {
8 | node: true,
9 | },
10 | rules: {
11 | "global-require": 0
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["prettier"],
3 | extends: [
4 | 'airbnb-base',
5 | 'plugin:prettier/recommended'
6 | ],
7 | env: {
8 | node: true,
9 | },
10 | rules: {
11 | "global-require": 0
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine as builder
2 |
3 | WORKDIR /app
4 |
5 | COPY ./package.json .
6 | RUN npm install
7 |
8 | COPY . .
9 | RUN npm run build
10 |
11 | FROM nginx:stable-alpine
12 |
13 | EXPOSE 80
14 |
15 | COPY --from=builder /app/dist /usr/share/nginx/html
--------------------------------------------------------------------------------
/notes-api/api/api/index.js:
--------------------------------------------------------------------------------
1 | const { Router } = require('express');
2 |
3 | const routes = Router();
4 |
5 | routes.get('/', (req, res) => {
6 | res.status(200).json({
7 | error: false,
8 | message: 'Bonjour, mon ami',
9 | });
10 | });
11 |
12 | require('./routes/notes')(routes);
13 |
14 | module.exports = routes;
15 |
--------------------------------------------------------------------------------
/hello-dock/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-dock",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build"
7 | },
8 | "dependencies": {
9 | "vue": "^3.0.0-rc.1"
10 | },
11 | "devDependencies": {
12 | "vite": "^1.0.0-rc.1",
13 | "@vue/compiler-sfc": "^3.0.0-rc.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/api/index.js:
--------------------------------------------------------------------------------
1 | const { Router } = require('express');
2 |
3 | const routes = Router();
4 |
5 | routes.get('/', (req, res) => {
6 | res.status(200).json({
7 | error: false,
8 | message: 'Bonjour, mon ami',
9 | });
10 | });
11 |
12 | require('./routes/notes')(routes);
13 |
14 | module.exports = routes;
15 |
--------------------------------------------------------------------------------
/hello-dock/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
16 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import store from './store';
5 |
6 | import 'mini.css/dist/mini-default.min.css';
7 |
8 | Vue.config.productionTip = false;
9 |
10 | new Vue({
11 | router,
12 | store,
13 | render: (h) => h(App),
14 | }).$mount('#app');
15 |
--------------------------------------------------------------------------------
/fullstack-notes-application/nginx/production.conf:
--------------------------------------------------------------------------------
1 | upstream client {
2 | server notes-client:80;
3 | }
4 |
5 | upstream api {
6 | server notes-api:3000;
7 | }
8 |
9 | server {
10 | location / {
11 | proxy_pass http://client;
12 | }
13 |
14 | location /api {
15 | rewrite /api/(.*) /$1 break;
16 | proxy_pass http://api;
17 | }
18 | }
--------------------------------------------------------------------------------
/fullstack-notes-application/client/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devServer: {
3 | disableHostCheck: true,
4 | },
5 | chainWebpack: (config) => {
6 | config
7 | .plugin('html')
8 | .tap((args) => {
9 | // eslint-disable-next-line no-param-reassign
10 | args[0].title = 'Notes';
11 | return args;
12 | });
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/hello-dock/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The Docker Handbook
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/notes-api/api/migrations/20200619195837_create_notes_table.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable func-names */
2 |
3 | exports.up = function (knex) {
4 | return knex.schema.createTable('notes', (table) => {
5 | table.increments();
6 | table.string('title').notNullable();
7 | table.text('content').notNullable();
8 | });
9 | };
10 |
11 | exports.down = function (knex) {
12 | return knex.schema.dropTable('notes');
13 | };
14 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/migrations/20200619195837_create_notes_table.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable func-names */
2 |
3 | exports.up = function (knex) {
4 | return knex.schema.createTable('notes', (table) => {
5 | table.increments();
6 | table.string('title').notNullable();
7 | table.text('content').notNullable();
8 | });
9 | };
10 |
11 | exports.down = function (knex) {
12 | return knex.schema.dropTable('notes');
13 | };
14 |
--------------------------------------------------------------------------------
/notes-api/api/tests/e2e/api/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | const request = require('supertest');
4 |
5 | const app = require('../../../app');
6 |
7 | describe('GET /', () => {
8 | test('Responds with 200 status code and a message', async () => {
9 | const response = await request(app).get('/');
10 |
11 | expect(response.status).toBe(200);
12 | expect(response.body.message).toEqual('Bonjour, mon ami');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/tests/e2e/api/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | const request = require('supertest');
4 |
5 | const app = require('../../../app');
6 |
7 | describe('GET /', () => {
8 | test('Responds with 200 status code and a message', async () => {
9 | const response = await request(app).get('/');
10 |
11 | expect(response.status).toBe(200);
12 | expect(response.body.message).toEqual('Bonjour, mon ami');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/components/Note.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ note.title }}
4 |
{{ note.content }}
5 |
6 |
7 |
8 |
9 |
22 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Notes
5 | Home
6 | Create
7 |
8 |
9 |
10 |
11 |
12 |
19 |
--------------------------------------------------------------------------------
/notes-api/Makefile:
--------------------------------------------------------------------------------
1 | #################
2 | ## Production ##
3 | ################
4 | start:
5 | ./boot.sh
6 | build:
7 | ./build.sh
8 | stop:
9 | ./shutdown.sh
10 | destroy: stop
11 | ./destroy.sh
12 |
13 | ##################
14 | ## Development ##
15 | #################
16 | dev-start:
17 | docker-compose up --detach
18 | dev-build:
19 | docker-compose up --detach --build; docker-compose exec api npm run db:migrate
20 | dev-shell:
21 | docker-compose exec api bash
22 | dev-stop:
23 | docker-compose stop
24 | dev-destroy:
25 | docker-compose down --volume
--------------------------------------------------------------------------------
/fullstack-notes-application/api/Dockerfile:
--------------------------------------------------------------------------------
1 | # stage one
2 | FROM node:lts-alpine as builder
3 |
4 | # install dependencies for node-gyp
5 | RUN apk add --no-cache python2 make g++
6 |
7 | WORKDIR /app
8 |
9 | COPY ./package.json .
10 | RUN npm install --only=prod
11 |
12 | # stage two
13 | FROM node:lts-alpine
14 |
15 | EXPOSE 3000
16 | ENV NODE_ENV=production
17 |
18 | USER node
19 | RUN mkdir -p /home/node/app
20 | WORKDIR /home/node/app
21 |
22 | COPY . .
23 | COPY --from=builder /app/node_modules /home/node/app/node_modules
24 |
25 | CMD [ "node", "bin/www" ]
26 |
--------------------------------------------------------------------------------
/fullstack-notes-application/Makefile:
--------------------------------------------------------------------------------
1 | #################
2 | ## Production ##
3 | ################
4 | start:
5 | ./boot.sh
6 | build:
7 | ./build.sh
8 | stop:
9 | ./shutdown.sh
10 | destroy: stop
11 | ./destroy.sh
12 |
13 | ##################
14 | ## Development ##
15 | #################
16 | dev-start:
17 | docker-compose up --detach
18 | dev-build:
19 | docker-compose up --detach --build; docker-compose exec api npm run db:migrate
20 | dev-shell:
21 | docker-compose exec api bash
22 | dev-stop:
23 | docker-compose stop
24 | dev-destroy:
25 | docker-compose down --volume
--------------------------------------------------------------------------------
/fullstack-notes-application/nginx/development.conf:
--------------------------------------------------------------------------------
1 | upstream client {
2 | server client:8080;
3 | }
4 |
5 | upstream api {
6 | server api:3000;
7 | }
8 |
9 | server {
10 | location / {
11 | proxy_pass http://client;
12 | }
13 |
14 | location /sockjs-node {
15 | proxy_pass http://client;
16 | proxy_http_version 1.1;
17 | proxy_set_header Upgrade $http_upgrade;
18 | proxy_set_header Connection "Upgrade";
19 | }
20 |
21 | location /api {
22 | rewrite /api/(.*) /$1 break;
23 | proxy_pass http://api;
24 | }
25 | }
--------------------------------------------------------------------------------
/fullstack-notes-application/api/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # stage one
2 | FROM node:lts-alpine as builder
3 |
4 | # install dependencies for node-gyp
5 | RUN apk add --no-cache python2 make g++
6 |
7 | WORKDIR /app
8 |
9 | COPY ./package.json .
10 | RUN npm install
11 |
12 | # stage two
13 | FROM node:lts-alpine
14 |
15 | ENV NODE_ENV=development
16 |
17 | USER node
18 | RUN mkdir -p /home/node/app
19 | WORKDIR /home/node/app
20 |
21 | COPY . .
22 | COPY --from=builder /app/node_modules /home/node/app/node_modules
23 |
24 | CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]
25 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueRouter from 'vue-router';
3 | import Home from '../views/Home.vue';
4 |
5 | Vue.use(VueRouter);
6 |
7 | const routes = [
8 | {
9 | path: '/',
10 | name: 'Home',
11 | component: Home,
12 | },
13 | {
14 | path: '/notes/create',
15 | name: 'Create',
16 | component: () => import('../views/Create.vue'),
17 | },
18 | ];
19 |
20 | const router = new VueRouter({
21 | mode: 'history',
22 | base: process.env.BASE_URL,
23 | routes,
24 | });
25 |
26 | export default router;
27 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/tests/unit/note.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils';
2 | import Note from '@/components/Note.vue';
3 |
4 | describe('Note.vue', () => {
5 | it('renders props.note when passed', () => {
6 | const note = {
7 | title: 'Lorem ipsum',
8 | content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
9 | };
10 | const wrapper = shallowMount(Note, {
11 | propsData: { note },
12 | });
13 | expect(wrapper.props().note).toBe(note);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Empty!
8 |
Awkward silence!
9 |
10 |
11 |
12 |
13 |
28 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/airbnb',
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint',
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 | },
17 | overrides: [
18 | {
19 | files: [
20 | '**/__tests__/*.{j,t}s?(x)',
21 | '**/tests/unit/**/*.spec.{j,t}s?(x)',
22 | ],
23 | env: {
24 | jest: true,
25 | },
26 | },
27 | ],
28 | };
29 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/notes-api/api/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | const http = require('http');
8 | const dotenv = require('dotenv');
9 | const app = require('../app');
10 |
11 | dotenv.config();
12 |
13 | /**
14 | * Get port from environment and store in Express.
15 | */
16 |
17 | const host = process.env.HOST || 'http://127.0.0.1';
18 | const port = process.env.PORT || 3000;
19 | app.set('port', port);
20 |
21 | /**
22 | * Create HTTP server.
23 | */
24 |
25 | const server = http.createServer(app);
26 |
27 | /**
28 | * Listen on provided port, on all network interfaces.
29 | */
30 | // eslint-disable-next-line no-console
31 | console.log(`app running -> ${host}:${port}`);
32 | server.listen(port);
33 |
--------------------------------------------------------------------------------
/notes-api/shutdown.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | API_CONTAINER_NAME="notes-api"
5 | DB_CONTAINER_NAME="notes-db"
6 |
7 | if docker container ls | grep -q $API_CONTAINER_NAME;
8 | then
9 | printf "stopping api container --->\n"
10 | docker container stop $API_CONTAINER_NAME;
11 | printf "api container stopped --->\n"
12 | else
13 | printf "api container not found --->\n"
14 | fi
15 |
16 | printf "\n"
17 |
18 | if docker container ls | grep -q $DB_CONTAINER_NAME;
19 | then
20 | printf "stopping db container --->\n"
21 | docker container stop $DB_CONTAINER_NAME;
22 | printf "db container stopped --->\n"
23 | else
24 | printf "db container not found --->\n"
25 | fi
26 |
27 | printf "\n"
28 |
29 | printf "shutdown script finished\n\n"
--------------------------------------------------------------------------------
/notes-api/boot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | API_CONTAINER_NAME="notes-api"
5 | DB_CONTAINER_NAME="notes-db"
6 |
7 | if docker container ls --all | grep -q $DB_CONTAINER_NAME;
8 | then
9 | printf "starting db container --->\n"
10 | docker container start $DB_CONTAINER_NAME;
11 | printf "db container started --->\n"
12 | else
13 | printf "db container not found --->\n"
14 | fi
15 |
16 | printf "\n"
17 |
18 | if docker container ls --all | grep -q $API_CONTAINER_NAME;
19 | then
20 | printf "starting api container --->\n"
21 | docker container start $API_CONTAINER_NAME;
22 | printf "api container started --->\n"
23 | else
24 | printf "api container not found --->\n"
25 | fi
26 |
27 | printf "\n"
28 |
29 | printf "boot script finished\n\n"
--------------------------------------------------------------------------------
/fullstack-notes-application/api/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | const http = require('http');
8 | const dotenv = require('dotenv');
9 | const app = require('../app');
10 |
11 | dotenv.config();
12 |
13 | /**
14 | * Get port from environment and store in Express.
15 | */
16 |
17 | const host = process.env.HOST || 'http://127.0.0.1';
18 | const port = process.env.PORT || 3000;
19 | app.set('port', port);
20 |
21 | /**
22 | * Create HTTP server.
23 | */
24 |
25 | const server = http.createServer(app);
26 |
27 | /**
28 | * Listen on provided port, on all network interfaces.
29 | */
30 | // eslint-disable-next-line no-console
31 | console.log(`app running -> ${host}:${port}`);
32 | server.listen(port);
33 |
--------------------------------------------------------------------------------
/hello-dock/src/components/HelloDock.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ msg }}
3 | An in-depth guide to application containerization with Docker
4 |
5 |
6 | Author: Farhan Hasin Chowdhury
7 |
8 | Website: https://farhan.info/
9 |
10 | LinkedIn: /in/farhanhasin
11 |
12 | Twitter: @frhnhsin
13 |
14 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/notes-api/api/knexfile.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | development: {
3 | client: 'pg',
4 | connection: {
5 | host: process.env.DB_HOST,
6 | port: 5432,
7 | user: 'postgres',
8 | password: process.env.DB_PASSWORD,
9 | database: process.env.DB_DATABASE,
10 | },
11 | },
12 |
13 | test: {
14 | client: 'sqlite3',
15 | connection: ':memory:',
16 | useNullAsDefault: true,
17 | },
18 |
19 | staging: {
20 | client: 'pg',
21 | connection: {
22 | host: process.env.DB_HOST,
23 | port: 5432,
24 | user: 'postgres',
25 | password: process.env.DB_PASSWORD,
26 | database: process.env.DB_DATABASE,
27 | },
28 | },
29 |
30 | production: {
31 | client: 'pg',
32 | connection: {
33 | host: process.env.DB_HOST,
34 | port: 5432,
35 | user: 'postgres',
36 | password: process.env.DB_PASSWORD,
37 | database: process.env.DB_DATABASE,
38 | },
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/knexfile.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | development: {
3 | client: 'pg',
4 | connection: {
5 | host: process.env.DB_HOST,
6 | port: 5432,
7 | user: 'postgres',
8 | password: process.env.DB_PASSWORD,
9 | database: process.env.DB_DATABASE,
10 | },
11 | },
12 |
13 | test: {
14 | client: 'sqlite3',
15 | connection: ':memory:',
16 | useNullAsDefault: true,
17 | },
18 |
19 | staging: {
20 | client: 'pg',
21 | connection: {
22 | host: process.env.DB_HOST,
23 | port: 5432,
24 | user: 'postgres',
25 | password: process.env.DB_PASSWORD,
26 | database: process.env.DB_DATABASE,
27 | },
28 | },
29 |
30 | production: {
31 | client: 'pg',
32 | connection: {
33 | host: process.env.DB_HOST,
34 | port: 5432,
35 | user: 'postgres',
36 | password: process.env.DB_PASSWORD,
37 | database: process.env.DB_DATABASE,
38 | },
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/views/Create.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
37 |
--------------------------------------------------------------------------------
/notes-api/api/services/notes.js:
--------------------------------------------------------------------------------
1 | module.exports = class NotesService {
2 | constructor(Note) {
3 | this.Note = Note;
4 | }
5 |
6 | async index() {
7 | return await this.Note.query();
8 | }
9 |
10 | async store(params) {
11 | return await this.Note.query().insert({
12 | title: params.title,
13 | content: params.content,
14 | });
15 | }
16 |
17 | async show(id) {
18 | const note = await this.Note.query().findById(id);
19 |
20 | if (!note) {
21 | const err = new Error('Not Found!');
22 | err.status = 404;
23 | throw err;
24 | }
25 |
26 | return note;
27 | }
28 |
29 | async update(id, params) {
30 | const note = await this.Note.query().findById(id).patch({
31 | title: params.title,
32 | content: params.content,
33 | });
34 |
35 | if (!note) {
36 | const err = new Error('Not Found!');
37 | err.status = 404;
38 | throw err;
39 | }
40 |
41 | return note;
42 | }
43 |
44 | async destroy(id) {
45 | const note = await this.Note.query().deleteById(id);
46 |
47 | if (!note) {
48 | const err = new Error('Not Found!');
49 | err.status = 404;
50 | throw err;
51 | }
52 |
53 | return note;
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/services/notes.js:
--------------------------------------------------------------------------------
1 | module.exports = class NotesService {
2 | constructor(Note) {
3 | this.Note = Note;
4 | }
5 |
6 | async index() {
7 | return await this.Note.query();
8 | }
9 |
10 | async store(params) {
11 | return await this.Note.query().insert({
12 | title: params.title,
13 | content: params.content,
14 | });
15 | }
16 |
17 | async show(id) {
18 | const note = await this.Note.query().findById(id);
19 |
20 | if (!note) {
21 | const err = new Error('Not Found!');
22 | err.status = 404;
23 | throw err;
24 | }
25 |
26 | return note;
27 | }
28 |
29 | async update(id, params) {
30 | const note = await this.Note.query().findById(id).patch({
31 | title: params.title,
32 | content: params.content,
33 | });
34 |
35 | if (!note) {
36 | const err = new Error('Not Found!');
37 | err.status = 404;
38 | throw err;
39 | }
40 |
41 | return note;
42 | }
43 |
44 | async destroy(id) {
45 | const note = await this.Note.query().deleteById(id);
46 |
47 | if (!note) {
48 | const err = new Error('Not Found!');
49 | err.status = 404;
50 | throw err;
51 | }
52 |
53 | return note;
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "test:unit": "vue-cli-service test:unit",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "axios": "^0.19.2",
13 | "core-js": "^3.6.5",
14 | "mini.css": "^3.0.1",
15 | "vue": "^2.6.11",
16 | "vue-router": "^3.2.0",
17 | "vuex": "^3.4.0"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "~4.4.0",
21 | "@vue/cli-plugin-eslint": "~4.4.0",
22 | "@vue/cli-plugin-router": "~4.4.0",
23 | "@vue/cli-plugin-unit-jest": "~4.4.0",
24 | "@vue/cli-plugin-vuex": "~4.4.0",
25 | "@vue/cli-service": "~4.4.0",
26 | "@vue/eslint-config-airbnb": "^5.0.2",
27 | "@vue/test-utils": "^1.0.3",
28 | "babel-eslint": "^10.1.0",
29 | "css-loader": "^3.6.0",
30 | "eslint": "^6.7.2",
31 | "eslint-plugin-import": "^2.20.2",
32 | "eslint-plugin-vue": "^6.2.2",
33 | "lint-staged": "^9.5.0",
34 | "vue-template-compiler": "^2.6.11"
35 | },
36 | "gitHooks": {
37 | "pre-commit": "lint-staged"
38 | },
39 | "lint-staged": {
40 | "*.{js,jsx,vue}": [
41 | "vue-cli-service lint",
42 | "git add"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/notes-api/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notes-api",
3 | "main": "bin/www",
4 | "scripts": {
5 | "knex": "knex",
6 | "start": "cross-env NODE_ENV=production node bin/www",
7 | "dev": "cross-env NODE_ENV=development nodemon bin/www",
8 | "db:migrate": "knex migrate:latest",
9 | "db:refresh": "knex migrate:rollback --all; knex migrate:latest",
10 | "lint": "eslint --ext .js --cache --fix --ignore-path .gitignore .",
11 | "test": "cross-env NODE_ENV=test jest"
12 | },
13 | "dependencies": {
14 | "@hapi/joi": "^17.1.1",
15 | "celebrate": "^12.1.1",
16 | "cors": "^2.8.5",
17 | "cross-env": "^7.0.2",
18 | "dotenv": "^8.2.0",
19 | "express": "^4.17.1",
20 | "helmet": "^3.22.0",
21 | "knex": "^0.21.1",
22 | "morgan": "^1.10.0",
23 | "objection": "^2.2.0",
24 | "pg": "^8.2.1"
25 | },
26 | "devDependencies": {
27 | "eslint": "^6.8.0",
28 | "eslint-config-airbnb-base": "^14.1.0",
29 | "eslint-config-prettier": "^6.11.0",
30 | "eslint-plugin-import": "^2.20.2",
31 | "eslint-plugin-prettier": "^3.1.3",
32 | "jest": "^25.5.4",
33 | "lint-staged": "^10.1.7",
34 | "nodemon": "^2.0.3",
35 | "prettier": "^2.0.5",
36 | "sqlite3": "^5.0.0",
37 | "supertest": "^4.0.2"
38 | },
39 | "lint-staged": {
40 | "*.{js,}": "eslint --cache --fix"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/notes-api/destroy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | NETWORK_NAME="notes-api-network"
5 | DB_CONTAINER_VOLUME_NAME="notes-db-data"
6 | API_CONTAINER_NAME="notes-api"
7 | DB_CONTAINER_NAME="notes-db"
8 |
9 | if docker container ls --all | grep -q $API_CONTAINER_NAME;
10 | then
11 | printf "removing api container --->\n"
12 | docker container rm $API_CONTAINER_NAME;
13 | printf "api container removed --->\n"
14 | else
15 | printf "api container not found --->\n"
16 | fi
17 |
18 | printf "\n"
19 |
20 | if docker container ls --all | grep -q $DB_CONTAINER_NAME;
21 | then
22 | printf "removing db container --->\n"
23 | docker container rm $DB_CONTAINER_NAME;
24 | printf "db container removed --->\n"
25 | else
26 | printf "db container not found --->\n"
27 | fi
28 |
29 | printf "\n"
30 |
31 | if docker volume ls | grep -q $DB_CONTAINER_VOLUME_NAME;
32 | then
33 | printf "removing db data volume --->\n"
34 | docker volume rm $DB_CONTAINER_VOLUME_NAME;
35 | printf "db data volume removed --->\n"
36 | else
37 | printf "db data volume not found --->\n"
38 | fi
39 |
40 | printf "\n"
41 |
42 | if docker network ls | grep -q $NETWORK_NAME;
43 | then
44 | printf "removing network --->\n"
45 | docker network rm $NETWORK_NAME;
46 | printf "network removed --->\n"
47 | else
48 | printf "network not found --->\n"
49 | fi
50 |
51 | printf "\n"
52 |
53 | printf "destroy script finished\n\n"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker Handbook Projects
2 |
3 | 
4 |
5 | | :bell: NOTIFICATION |
6 | |:--------------------|
7 | | There are two branches in this repository. The [master](https://github.com/fhsinchy/docker-handbook-projects/tree/master/) branch contains the starter projects and the [completed](https://github.com/fhsinchy/docker-handbook-projects/tree/completed/) branch contains the completed projects. |
8 |
9 | This repository holds the code for my [__Docker Handbook__](https://www.freecodecamp.org/news/the-docker-handbook/) article on [__freeCodecamp__](https://freecodecamp.org). In the article the readers work through __six__ projects with increasing complexity. These projects are as follows:
10 |
11 | - custom-nginx - A custom NGINX image based on the official [alpine](https://hub.docker.com/_/alpine/) image.
12 | - rmbyext - An executable image project.
13 | - hello-dock - A single container Vue application.
14 | - notes-api - A multi container Express API.
15 | - fullstack-notes-application - A full-stack CRUD application with [nginx](https://hub.docker.com/_/nginx/) as a reverse proxy.
16 |
17 | ## Prerequisites
18 |
19 | - Familiarity with the Linux Terminal.
20 | - Familiarity with JavaScript (some of the later projects use JavaScript).
21 |
22 | It's fine if you haven't worked with JavaScript that much. Having a basic knowledge of executing scripts with `npm` will suffice.
23 |
--------------------------------------------------------------------------------
/fullstack-notes-application/shutdown.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | API_CONTAINER_NAME="notes-api"
5 | CLIENT_CONTAINER_NAME="notes-client"
6 | ROUTER_CONTAINER_NAME="notes-router"
7 | DB_CONTAINER_NAME="notes-db"
8 |
9 | if docker container ls | grep -q $ROUTER_CONTAINER_NAME;
10 | then
11 | printf "stopping router container --->\n"
12 | docker container stop $ROUTER_CONTAINER_NAME;
13 | printf "router container stopped --->\n"
14 | else
15 | printf "router container not found --->\n"
16 | fi
17 |
18 | printf "\n"
19 |
20 | if docker container ls | grep -q $CLIENT_CONTAINER_NAME;
21 | then
22 | printf "stopping client container --->\n"
23 | docker container stop $CLIENT_CONTAINER_NAME;
24 | printf "client container stopped --->\n"
25 | else
26 | printf "client container not found --->\n"
27 | fi
28 |
29 | printf "\n"
30 |
31 | if docker container ls | grep -q $API_CONTAINER_NAME;
32 | then
33 | printf "stopping api container --->\n"
34 | docker container stop $API_CONTAINER_NAME;
35 | printf "api container stopped --->\n"
36 | else
37 | printf "api container not found --->\n"
38 | fi
39 |
40 | printf "\n"
41 |
42 | if docker container ls | grep -q $DB_CONTAINER_NAME;
43 | then
44 | printf "stopping db container --->\n"
45 | docker container stop $DB_CONTAINER_NAME;
46 | printf "db container stopped --->\n"
47 | else
48 | printf "db container not found --->\n"
49 | fi
50 |
51 | printf "\n"
52 |
53 | printf "shutdown script finished\n\n"
--------------------------------------------------------------------------------
/fullstack-notes-application/boot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | API_CONTAINER_NAME="notes-api"
5 | CLIENT_CONTAINER_NAME="notes-client"
6 | ROUTER_CONTAINER_NAME="notes-router"
7 | DB_CONTAINER_NAME="notes-db"
8 |
9 | if docker container ls --all | grep -q $DB_CONTAINER_NAME;
10 | then
11 | printf "starting db container --->\n"
12 | docker container start $DB_CONTAINER_NAME;
13 | printf "db container started --->\n"
14 | else
15 | printf "db container not found --->\n"
16 | fi
17 |
18 | printf "\n"
19 |
20 | if docker container ls --all | grep -q $API_CONTAINER_NAME;
21 | then
22 | printf "starting api container --->\n"
23 | docker container start $API_CONTAINER_NAME;
24 | printf "api container started --->\n"
25 | else
26 | printf "api container not found --->\n"
27 | fi
28 |
29 | printf "\n"
30 |
31 | if docker container ls --all | grep -q $CLIENT_CONTAINER_NAME;
32 | then
33 | printf "starting client container --->\n"
34 | docker container start $CLIENT_CONTAINER_NAME;
35 | printf "client container started --->\n"
36 | else
37 | printf "client container not found --->\n"
38 | fi
39 |
40 | printf "\n"
41 |
42 | if docker container ls --all | grep -q $ROUTER_CONTAINER_NAME;
43 | then
44 | printf "starting router container --->\n"
45 | docker container start $ROUTER_CONTAINER_NAME;
46 | printf "router container started --->\n"
47 | else
48 | printf "router container not found --->\n"
49 | fi
50 |
51 | printf "\n"
52 |
53 | printf "boot script finished\n\n"
--------------------------------------------------------------------------------
/fullstack-notes-application/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "api",
3 | "version": "1.0.0",
4 | "main": "bin/www",
5 | "scripts": {
6 | "knex": "knex",
7 | "start": "cross-env NODE_ENV=production node bin/www",
8 | "dev": "cross-env NODE_ENV=development nodemon bin/www",
9 | "db:migrate": "knex migrate:latest",
10 | "db:refresh": "knex migrate:rollback --all; knex migrate:latest",
11 | "lint": "eslint --ext .js --cache --fix --ignore-path .gitignore .",
12 | "test": "cross-env NODE_ENV=test jest"
13 | },
14 | "keywords": [],
15 | "author": "Farhan Hasin Chowdhury ",
16 | "license": "MIT",
17 | "dependencies": {
18 | "@hapi/joi": "^17.1.1",
19 | "celebrate": "^12.1.1",
20 | "cors": "^2.8.5",
21 | "cross-env": "^7.0.2",
22 | "dotenv": "^8.2.0",
23 | "express": "^4.17.1",
24 | "helmet": "^3.22.0",
25 | "knex": "^0.21.1",
26 | "morgan": "^1.10.0",
27 | "objection": "^2.2.0",
28 | "pg": "^8.2.1"
29 | },
30 | "devDependencies": {
31 | "eslint": "^6.8.0",
32 | "eslint-config-airbnb-base": "^14.1.0",
33 | "eslint-config-prettier": "^6.11.0",
34 | "eslint-plugin-import": "^2.20.2",
35 | "eslint-plugin-prettier": "^3.1.3",
36 | "jest": "^25.5.4",
37 | "lint-staged": "^10.1.7",
38 | "nodemon": "^2.0.3",
39 | "prettier": "^2.0.5",
40 | "sqlite3": "^5.0.0",
41 | "supertest": "^4.0.2"
42 | },
43 | "lint-staged": {
44 | "*.{js,}": "eslint --cache --fix"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/fullstack-notes-application/client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import axios from '../libs/axios';
4 | import router from '../router';
5 |
6 | Vue.use(Vuex);
7 |
8 | export default new Vuex.Store({
9 | state: {
10 | notes: [],
11 | },
12 | mutations: {
13 | insertNote(state, payload) {
14 | state.notes.push(payload);
15 | },
16 | deleteNote(state, payload) {
17 | state.notes.splice(state.notes.indexOf(payload), 1);
18 | },
19 | },
20 | actions: {
21 | async fetchNotes(context) {
22 | try {
23 | const response = await axios.get('/notes');
24 |
25 | response.data.data.notes.map((note) => context.commit('insertNote', note));
26 | } catch (error) {
27 | // eslint-disable-next-line no-console
28 | console.log(error);
29 | }
30 | },
31 | async createNote(context, payload) {
32 | try {
33 | const response = await axios.post('/notes', payload);
34 |
35 | context.commit('insertNote', response.data.data.note);
36 |
37 | router.push('/');
38 | } catch (error) {
39 | // eslint-disable-next-line no-console
40 | console.log(error);
41 | }
42 | },
43 | async deleteNote(context, payload) {
44 | try {
45 | await axios.delete(`/notes/${payload.id}`);
46 |
47 | context.commit('deleteNote', payload);
48 | } catch (error) {
49 | // eslint-disable-next-line no-console
50 | console.log(error);
51 | }
52 | },
53 | },
54 | modules: {
55 | },
56 | });
57 |
--------------------------------------------------------------------------------
/notes-api/api/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | const cors = require('cors');
6 | const logger = require('morgan');
7 | const helmet = require('helmet');
8 | const express = require('express');
9 | const { Model } = require('objection');
10 | const { isCelebrate } = require('celebrate');
11 |
12 | const routes = require('./api');
13 | const { Knex } = require('./services');
14 |
15 | /**
16 | * ORM initialization.
17 | */
18 |
19 | Model.knex(Knex);
20 |
21 | /**
22 | * app instance initialization.
23 | */
24 |
25 | const app = express();
26 |
27 | /**
28 | * Middleware registration.
29 | */
30 |
31 | app.use(cors());
32 | app.use(helmet());
33 | app.use(logger('dev'));
34 | app.use(express.json());
35 |
36 | /**
37 | * Route registration.
38 | */
39 |
40 | app.use('/', routes);
41 |
42 | /**
43 | * 404 handler.
44 | */
45 |
46 | app.use((req, res, next) => {
47 | const err = new Error('Not Found!');
48 | err.status = 404;
49 | next(err);
50 | });
51 |
52 | /**
53 | * Error handler registration.
54 | */
55 |
56 | // eslint-disable-next-line no-unused-vars
57 | app.use((err, req, res, next) => {
58 | const status = isCelebrate(err) ? 400 : err.status || 500;
59 | const message =
60 | process.env.NODE_ENV === 'production' && err.status === 500
61 | ? 'Something Went Wrong!'
62 | : err.message;
63 |
64 | // eslint-disable-next-line no-console
65 | if (status === 500) console.log(err.stack);
66 |
67 | res.status(status).json({
68 | status: status >= 500 ? 'error' : 'fail',
69 | message,
70 | });
71 | });
72 |
73 | module.exports = app;
74 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | const cors = require('cors');
6 | const logger = require('morgan');
7 | const helmet = require('helmet');
8 | const express = require('express');
9 | const { Model } = require('objection');
10 | const { isCelebrate } = require('celebrate');
11 |
12 | const routes = require('./api');
13 | const { Knex } = require('./services');
14 |
15 | /**
16 | * ORM initialization.
17 | */
18 |
19 | Model.knex(Knex);
20 |
21 | /**
22 | * app instance initialization.
23 | */
24 |
25 | const app = express();
26 |
27 | /**
28 | * Middleware registration.
29 | */
30 |
31 | app.use(cors());
32 | app.use(helmet());
33 | app.use(logger('dev'));
34 | app.use(express.json());
35 |
36 | /**
37 | * Route registration.
38 | */
39 |
40 | app.use('/', routes);
41 |
42 | /**
43 | * 404 handler.
44 | */
45 |
46 | app.use((req, res, next) => {
47 | const err = new Error('Not Found!');
48 | err.status = 404;
49 | next(err);
50 | });
51 |
52 | /**
53 | * Error handler registration.
54 | */
55 |
56 | // eslint-disable-next-line no-unused-vars
57 | app.use((err, req, res, next) => {
58 | const status = isCelebrate(err) ? 400 : err.status || 500;
59 | const message =
60 | process.env.NODE_ENV === 'production' && err.status === 500
61 | ? 'Something Went Wrong!'
62 | : err.message;
63 |
64 | // eslint-disable-next-line no-console
65 | if (status === 500) console.log(err.stack);
66 |
67 | res.status(status).json({
68 | status: status >= 500 ? 'error' : 'fail',
69 | message,
70 | });
71 | });
72 |
73 | module.exports = app;
74 |
--------------------------------------------------------------------------------
/notes-api/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | NETWORK_NAME="notes-api-network"
5 | DB_CONTAINER_VOLUME_NAME="notes-db-data"
6 | API_IMAGE_NAME="notes-api"
7 | API_CONTAINER_NAME="notes-api"
8 | DB_CONTAINER_NAME="notes-db"
9 |
10 | DB_NAME="notesdb"
11 | DB_PASSWORD="secret"
12 |
13 | if docker network ls | grep -q $NETWORK_NAME;
14 | then
15 | printf "network found --->\n"
16 | else
17 | printf "creating network --->\n"
18 | docker network create $NETWORK_NAME;
19 | printf "network created --->\n"
20 | fi
21 |
22 | printf "\n"
23 |
24 | if docker volume ls | grep -q $DB_CONTAINER_VOLUME_NAME;
25 | then
26 | printf "volume found --->\n"
27 | else
28 | printf "creating volume --->\n"
29 | docker volume create $DB_CONTAINER_VOLUME_NAME;
30 | printf "volume created --->\n"
31 | fi
32 |
33 | printf "\n"
34 |
35 | printf "starting db container --->\n"
36 | if docker container ls --all | grep -q $DB_CONTAINER_NAME;
37 | then
38 | docker container start $DB_CONTAINER_NAME
39 | else
40 | docker container run \
41 | --detach \
42 | --volume $DB_CONTAINER_VOLUME_NAME:/var/lib/postgresql/data \
43 | --name=$DB_CONTAINER_NAME \
44 | --env POSTGRES_DB=$DB_NAME \
45 | --env POSTGRES_PASSWORD=$DB_PASSWORD \
46 | --network=$NETWORK_NAME \
47 | postgres:12;
48 | fi
49 | printf "db container started --->\n"
50 |
51 | printf "\n"
52 |
53 | cd api;
54 | printf "creating api image --->\n"
55 | docker image build . --tag $API_IMAGE_NAME;
56 | printf "api image created --->\n"
57 | printf "starting api container --->\n"
58 | if docker container ls --all | grep -q $API_CONTAINER_NAME;
59 | then
60 | docker container start $API_CONTAINER_NAME
61 | else
62 | docker container run \
63 | --detach \
64 | --name=$API_CONTAINER_NAME \
65 | --env DB_HOST=$DB_CONTAINER_NAME \
66 | --env DB_DATABASE=$DB_NAME \
67 | --env DB_PASSWORD=$DB_PASSWORD \
68 | --publish=3000:3000 \
69 | --network=$NETWORK_NAME \
70 | $API_IMAGE_NAME;
71 | docker container exec $API_CONTAINER_NAME npm run db:migrate;
72 | fi
73 | printf "api container started --->\n"
74 |
75 | cd ..
76 | printf "\n"
77 |
78 | printf "build script finished\n\n"
--------------------------------------------------------------------------------
/fullstack-notes-application/destroy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | NETWORK_NAME_BACKEND="fullstack-notes-application-network-backend"
5 | NETWORK_NAME_FRONTEND="fullstack-notes-application-network-frontend"
6 | DB_CONTAINER_VOLUME_NAME="notes-db-data"
7 | API_IMAGE_NAME="notes-api"
8 | CLIENT_IMAGE_NAME="notes-client"
9 | ROUTER_IMAGE_NAME="notes-router"
10 | API_CONTAINER_NAME="notes-api"
11 | CLIENT_CONTAINER_NAME="notes-client"
12 | ROUTER_CONTAINER_NAME="notes-router"
13 | DB_CONTAINER_NAME="notes-db"
14 |
15 | if docker container ls --all | grep -q $ROUTER_CONTAINER_NAME;
16 | then
17 | printf "removing router container --->\n"
18 | docker container rm $ROUTER_CONTAINER_NAME;
19 | printf "router container removed --->\n"
20 | else
21 | printf "router container not found --->\n"
22 | fi
23 |
24 | if docker container ls --all | grep -q $CLIENT_CONTAINER_NAME;
25 | then
26 | printf "removing client container --->\n"
27 | docker container rm $CLIENT_CONTAINER_NAME;
28 | printf "client container removed --->\n"
29 | else
30 | printf "client container not found --->\n"
31 | fi
32 |
33 | printf "\n"
34 |
35 | if docker container ls --all | grep -q $API_CONTAINER_NAME;
36 | then
37 | printf "removing api container --->\n"
38 | docker container rm $API_CONTAINER_NAME;
39 | printf "api container removed --->\n"
40 | else
41 | printf "api container not found --->\n"
42 | fi
43 |
44 | printf "\n"
45 |
46 | if docker container ls --all | grep -q $DB_CONTAINER_NAME;
47 | then
48 | printf "removing db container --->\n"
49 | docker container rm $DB_CONTAINER_NAME;
50 | printf "db container removed --->\n"
51 | else
52 | printf "db container not found --->\n"
53 | fi
54 |
55 | printf "\n"
56 |
57 | if docker volume ls | grep -q $DB_CONTAINER_VOLUME_NAME;
58 | then
59 | printf "removing db data volume --->\n"
60 | docker volume rm $DB_CONTAINER_VOLUME_NAME;
61 | printf "db data volume removed --->\n"
62 | else
63 | printf "db data volume not found --->\n"
64 | fi
65 |
66 | printf "\n"
67 |
68 | if docker network ls | grep -q $NETWORK_NAME_BACKEND;
69 | then
70 | printf "removing backend network --->\n"
71 | docker network rm $NETWORK_NAME_BACKEND;
72 | printf "backend network removed --->\n"
73 | else
74 | printf "backend network not found --->\n"
75 | fi
76 |
77 | printf "\n"
78 |
79 | if docker network ls | grep -q $NETWORK_NAME_FRONTEND;
80 | then
81 | printf "removing frontend network --->\n"
82 | docker network rm $NETWORK_NAME_FRONTEND;
83 | printf "frontend network removed --->\n"
84 | else
85 | printf "frontend network not found --->\n"
86 | fi
87 |
88 | printf "\n"
89 |
90 | printf "destroy script finished\n\n"
--------------------------------------------------------------------------------
/notes-api/api/api/routes/notes.js:
--------------------------------------------------------------------------------
1 | const { Router } = require('express');
2 | const { celebrate, Joi } = require('celebrate');
3 |
4 | const { Note } = require('../../models');
5 | const { NotesService } = require('../../services');
6 |
7 | const router = Router();
8 |
9 | module.exports = (routes) => {
10 | routes.use('/notes', router);
11 |
12 | router.get(
13 | '/',
14 | async (req, res, next) => {
15 | try {
16 | res.status(200).json({
17 | status: 'success',
18 | message: 'All Notes.',
19 | data: {
20 | notes: await new NotesService(Note).index(),
21 | },
22 | });
23 | } catch (err) {
24 | next(err);
25 | }
26 | },
27 | );
28 |
29 | router.post(
30 | '/',
31 | celebrate({
32 | body: Joi.object().keys({
33 | title: Joi.string().trim().required(),
34 | content: Joi.string().trim().required(),
35 | }),
36 | }),
37 | async (req, res, next) => {
38 | try {
39 | res.status(201).json({
40 | status: 'success',
41 | message: 'Note Created.',
42 | data: {
43 | note: await new NotesService(Note).store(req.body),
44 | },
45 | });
46 | } catch (err) {
47 | next(err);
48 | }
49 | },
50 | );
51 |
52 | router.get(
53 | '/:id',
54 | async (req, res, next) => {
55 | try {
56 | res.status(200).json({
57 | status: 'success',
58 | message: 'Single Note.',
59 | data: {
60 | note: await new NotesService(Note).show(req.params.id),
61 | },
62 | });
63 | } catch (err) {
64 | next(err);
65 | }
66 | },
67 | );
68 |
69 | router.put(
70 | '/:id',
71 | celebrate({
72 | body: Joi.object().keys({
73 | title: Joi.string().trim().required(),
74 | content: Joi.string().trim().required(),
75 | }),
76 | }),
77 | async (req, res, next) => {
78 | try {
79 | res.status(200).json({
80 | status: 'success',
81 | message: 'Note Updated.',
82 | data: {
83 | note: await new NotesService(Note).update(req.params.id, req.body),
84 | },
85 | });
86 | } catch (err) {
87 | next(err);
88 | }
89 | },
90 | );
91 |
92 | router.delete(
93 | '/:id',
94 | async (req, res, next) => {
95 | try {
96 | await new NotesService(Note).destroy(req.params.id)
97 |
98 | res.status(200).json({
99 | status: 'success',
100 | message: 'Note Deleted.',
101 | data: null,
102 | });
103 | } catch (err) {
104 | next(err);
105 | }
106 | },
107 | );
108 | };
109 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/api/routes/notes.js:
--------------------------------------------------------------------------------
1 | const { Router } = require('express');
2 | const { celebrate, Joi } = require('celebrate');
3 |
4 | const { Note } = require('../../models');
5 | const { NotesService } = require('../../services');
6 |
7 | const router = Router();
8 |
9 | module.exports = (routes) => {
10 | routes.use('/notes', router);
11 |
12 | router.get(
13 | '/',
14 | async (req, res, next) => {
15 | try {
16 | res.status(200).json({
17 | status: 'success',
18 | message: 'All Notes.',
19 | data: {
20 | notes: await new NotesService(Note).index(),
21 | },
22 | });
23 | } catch (err) {
24 | next(err);
25 | }
26 | },
27 | );
28 |
29 | router.post(
30 | '/',
31 | celebrate({
32 | body: Joi.object().keys({
33 | title: Joi.string().trim().required(),
34 | content: Joi.string().trim().required(),
35 | }),
36 | }),
37 | async (req, res, next) => {
38 | try {
39 | res.status(201).json({
40 | status: 'success',
41 | message: 'Note Created.',
42 | data: {
43 | note: await new NotesService(Note).store(req.body),
44 | },
45 | });
46 | } catch (err) {
47 | next(err);
48 | }
49 | },
50 | );
51 |
52 | router.get(
53 | '/:id',
54 | async (req, res, next) => {
55 | try {
56 | res.status(200).json({
57 | status: 'success',
58 | message: 'Single Note.',
59 | data: {
60 | note: await new NotesService(Note).show(req.params.id),
61 | },
62 | });
63 | } catch (err) {
64 | next(err);
65 | }
66 | },
67 | );
68 |
69 | router.put(
70 | '/:id',
71 | celebrate({
72 | body: Joi.object().keys({
73 | title: Joi.string().trim().required(),
74 | content: Joi.string().trim().required(),
75 | }),
76 | }),
77 | async (req, res, next) => {
78 | try {
79 | res.status(200).json({
80 | status: 'success',
81 | message: 'Note Updated.',
82 | data: {
83 | note: await new NotesService(Note).update(req.params.id, req.body),
84 | },
85 | });
86 | } catch (err) {
87 | next(err);
88 | }
89 | },
90 | );
91 |
92 | router.delete(
93 | '/:id',
94 | async (req, res, next) => {
95 | try {
96 | await new NotesService(Note).destroy(req.params.id)
97 |
98 | res.status(200).json({
99 | status: 'success',
100 | message: 'Note Deleted.',
101 | data: null,
102 | });
103 | } catch (err) {
104 | next(err);
105 | }
106 | },
107 | );
108 | };
109 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/node,vue,vuejs
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,vue,vuejs
4 |
5 | ### Node ###
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # TypeScript v1 declaration files
50 | typings/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variables file
77 | .env
78 | .env.test
79 |
80 | # parcel-bundler cache (https://parceljs.org/)
81 | .cache
82 |
83 | # Next.js build output
84 | .next
85 |
86 | # Nuxt.js build / generate output
87 | .nuxt
88 | dist
89 |
90 | # Gatsby files
91 | .cache/
92 | # Comment in the public line in if your project uses Gatsby and not Next.js
93 | # https://nextjs.org/blog/next-9-1#public-directory-support
94 | # public
95 |
96 | # vuepress build output
97 | .vuepress/dist
98 |
99 | # Serverless directories
100 | .serverless/
101 |
102 | # FuseBox cache
103 | .fusebox/
104 |
105 | # DynamoDB Local files
106 | .dynamodb/
107 |
108 | # TernJS port file
109 | .tern-port
110 |
111 | # Stores VSCode versions used for testing VSCode extensions
112 | .vscode-test
113 |
114 | ### Vue ###
115 | # gitignore template for Vue.js projects
116 | #
117 | # Recommended template: Node.gitignore
118 |
119 | # TODO: where does this rule come from?
120 | docs/_book
121 |
122 | # TODO: where does this rule come from?
123 | test/
124 |
125 | ### Vuejs ###
126 | # Recommended template: Node.gitignore
127 |
128 | dist/
129 | npm-debug.log
130 | yarn-error.log
131 |
132 | ### Vite ###
133 | node_modules
134 | .DS_Store
135 | dist
136 | *.local
137 |
138 | ## Docker ##
139 | docker-volumes-data
140 |
141 | # End of https://www.toptal.com/developers/gitignore/api/node,vue,vuejs
142 |
--------------------------------------------------------------------------------
/notes-api/api/tests/e2e/api/routes/notes.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | const request = require('supertest');
4 |
5 | const app = require('../../../../app');
6 | const { Knex } = require('../../../../services');
7 |
8 | require('dotenv').config();
9 |
10 | beforeEach(() => {
11 | return Knex.migrate.latest();
12 | });
13 |
14 | afterEach(() => {
15 | return Knex.migrate.rollback();
16 | });
17 |
18 | afterAll(() => {
19 | return Knex.destroy();
20 | });
21 |
22 | describe('GET /notes', () => {
23 | test('Responds with 200 status code', async () => {
24 | const response = await request(app).get('/notes');
25 |
26 | expect(response.status).toBe(200);
27 | expect(response.body.data);
28 | });
29 | });
30 |
31 | describe('POST /notes', () => {
32 | test('Creates an new note in the database', async () => {
33 | const note = {
34 | title: 'Lorem ipsum',
35 | content:
36 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
37 | };
38 | const response = await request(app).post('/notes').send(note);
39 |
40 | expect(response.status).toBe(201);
41 | });
42 | });
43 |
44 | describe('GET /notes/1', () => {
45 | test('Returns a single note from the database', async () => {
46 | const note = {
47 | title: 'Lorem ipsum',
48 | content:
49 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
50 | };
51 | await request(app).post('/notes').send(note);
52 |
53 | const response = await request(app).get('/notes/1');
54 |
55 | expect(response.status).toBe(200);
56 | });
57 | });
58 |
59 | describe('PUT /notes/1', () => {
60 | test('Updates a single note in the database', async () => {
61 | const note = {
62 | title: 'Lorem ipsum',
63 | content:
64 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
65 | };
66 | const updatedNote = {
67 | title: 'Lorem ipsum [UPDATED]',
68 | content:
69 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
70 | };
71 |
72 | await request(app).post('/notes').send(note);
73 |
74 | const response = await request(app).put('/notes/1').send(updatedNote);
75 |
76 | expect(response.status).toBe(200);
77 | });
78 | });
79 |
80 | describe('DELETE /notes/1', () => {
81 | test('Deletes a single note from the database', async () => {
82 | const note = {
83 | title: 'Lorem ipsum',
84 | content:
85 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
86 | };
87 |
88 | await request(app).post('/notes').send(note);
89 |
90 | const response = await request(app).delete('/notes/1');
91 |
92 | expect(response.status).toBe(200);
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/fullstack-notes-application/api/tests/e2e/api/routes/notes.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 |
3 | const request = require('supertest');
4 |
5 | const app = require('../../../../app');
6 | const { Knex } = require('../../../../services');
7 |
8 | require('dotenv').config();
9 |
10 | beforeEach(() => {
11 | return Knex.migrate.latest();
12 | });
13 |
14 | afterEach(() => {
15 | return Knex.migrate.rollback();
16 | });
17 |
18 | afterAll(() => {
19 | return Knex.destroy();
20 | });
21 |
22 | describe('GET /notes', () => {
23 | test('Responds with 200 status code', async () => {
24 | const response = await request(app).get('/notes');
25 |
26 | expect(response.status).toBe(200);
27 | expect(response.body.data);
28 | });
29 | });
30 |
31 | describe('POST /notes', () => {
32 | test('Creates an new note in the database', async () => {
33 | const note = {
34 | title: 'Lorem ipsum',
35 | content:
36 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
37 | };
38 | const response = await request(app).post('/notes').send(note);
39 |
40 | expect(response.status).toBe(201);
41 | });
42 | });
43 |
44 | describe('GET /notes/1', () => {
45 | test('Returns a single note from the database', async () => {
46 | const note = {
47 | title: 'Lorem ipsum',
48 | content:
49 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
50 | };
51 | await request(app).post('/notes').send(note);
52 |
53 | const response = await request(app).get('/notes/1');
54 |
55 | expect(response.status).toBe(200);
56 | });
57 | });
58 |
59 | describe('PUT /notes/1', () => {
60 | test('Updates a single note in the database', async () => {
61 | const note = {
62 | title: 'Lorem ipsum',
63 | content:
64 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
65 | };
66 | const updatedNote = {
67 | title: 'Lorem ipsum [UPDATED]',
68 | content:
69 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
70 | };
71 |
72 | await request(app).post('/notes').send(note);
73 |
74 | const response = await request(app).put('/notes/1').send(updatedNote);
75 |
76 | expect(response.status).toBe(200);
77 | });
78 | });
79 |
80 | describe('DELETE /notes/1', () => {
81 | test('Deletes a single note from the database', async () => {
82 | const note = {
83 | title: 'Lorem ipsum',
84 | content:
85 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
86 | };
87 |
88 | await request(app).post('/notes').send(note);
89 |
90 | const response = await request(app).delete('/notes/1');
91 |
92 | expect(response.status).toBe(200);
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/fullstack-notes-application/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | NETWORK_NAME_BACKEND="fullstack-notes-application-network-backend"
5 | NETWORK_NAME_FRONTEND="fullstack-notes-application-network-frontend"
6 | DB_CONTAINER_VOLUME_NAME="notes-db-data"
7 | API_IMAGE_NAME="notes-api"
8 | CLIENT_IMAGE_NAME="notes-client"
9 | ROUTER_IMAGE_NAME="notes-router"
10 | API_CONTAINER_NAME="notes-api"
11 | CLIENT_CONTAINER_NAME="notes-client"
12 | ROUTER_CONTAINER_NAME="notes-router"
13 | DB_CONTAINER_NAME="notes-db"
14 |
15 | DB_NAME="notesdb"
16 | DB_PASSWORD="secret"
17 |
18 | if docker network ls | grep -q $NETWORK_NAME_BACKEND;
19 | then
20 | printf "backend network found --->\n"
21 | else
22 | printf "creating backend network --->\n"
23 | docker network create $NETWORK_NAME_BACKEND;
24 | printf "backend network created --->\n"
25 | fi
26 |
27 | printf "\n"
28 |
29 | if docker volume ls | grep -q $DB_CONTAINER_VOLUME_NAME;
30 | then
31 | printf "volume found --->\n"
32 | else
33 | printf "creating volume --->\n"
34 | docker volume create $DB_CONTAINER_VOLUME_NAME;
35 | printf "volume created --->\n"
36 | fi
37 |
38 | printf "\n"
39 |
40 | printf "starting db container --->\n"
41 | if docker container ls --all | grep -q $DB_CONTAINER_NAME;
42 | then
43 | docker container start $DB_CONTAINER_NAME
44 | else
45 | docker container run \
46 | --detach \
47 | --volume $DB_CONTAINER_VOLUME_NAME:/var/lib/postgresql/data \
48 | --name=$DB_CONTAINER_NAME \
49 | --env POSTGRES_DB=$DB_NAME \
50 | --env POSTGRES_PASSWORD=$DB_PASSWORD \
51 | --network=$NETWORK_NAME_BACKEND \
52 | postgres:12;
53 | fi
54 | printf "db container started --->\n"
55 |
56 | printf "\n"
57 |
58 | cd api;
59 | printf "creating api image --->\n"
60 | docker image build . --tag $API_IMAGE_NAME;
61 | printf "api image created --->\n"
62 | printf "starting api container --->\n"
63 | if docker container ls --all | grep -q $API_CONTAINER_NAME;
64 | then
65 | docker container start $API_CONTAINER_NAME
66 | else
67 | docker container run \
68 | --detach \
69 | --name=$API_CONTAINER_NAME \
70 | --env DB_HOST=$DB_CONTAINER_NAME \
71 | --env DB_DATABASE=$DB_NAME \
72 | --env DB_PASSWORD=$DB_PASSWORD \
73 | --publish=3000:3000 \
74 | --network=$NETWORK_NAME_BACKEND \
75 | $API_IMAGE_NAME;
76 | docker container exec $API_CONTAINER_NAME npm run db:migrate;
77 | fi
78 | printf "api container started --->\n"
79 |
80 | cd ..
81 | printf "\n"
82 |
83 | if docker network ls | grep -q $NETWORK_NAME_FRONTEND;
84 | then
85 | printf "frontend network found --->\n"
86 | else
87 | printf "creating frontend network --->\n"
88 | docker network create $NETWORK_NAME_FRONTEND;
89 | printf "frontend network created --->\n"
90 | fi
91 |
92 | printf "\n"
93 |
94 | cd client;
95 | printf "creating client image --->\n"
96 | docker image build . --tag $CLIENT_IMAGE_NAME;
97 | printf "client image created --->\n"
98 | printf "starting client container --->\n"
99 | if docker container ls --all | grep -q $CLIENT_CONTAINER_NAME;
100 | then
101 | docker container start $CLIENT_CONTAINER_NAME
102 | else
103 | docker container run \
104 | --detach \
105 | --name=$CLIENT_CONTAINER_NAME \
106 | --network=$NETWORK_NAME_FRONTEND \
107 | $CLIENT_IMAGE_NAME;
108 | fi
109 | printf "client container started --->\n"
110 |
111 | cd ..
112 | printf "\n"
113 |
114 | cd nginx;
115 | printf "creating router image --->\n"
116 | docker image build . --tag $ROUTER_IMAGE_NAME;
117 | printf "router image created --->\n"
118 | if docker container ls --all | grep -q $ROUTER_CONTAINER_NAME;
119 | then
120 | printf "router container found --->\n"
121 | else
122 | printf "creating router container --->\n"
123 | docker container create \
124 | --publish=8080:80 \
125 | --name=$ROUTER_CONTAINER_NAME \
126 | $ROUTER_IMAGE_NAME;
127 | printf "router container created --->\n"
128 | printf "adding router to backend network --->\n"
129 | docker network connect $NETWORK_NAME_BACKEND $ROUTER_CONTAINER_NAME
130 | printf "router added to backend network --->\n"
131 | printf "adding router to frontend network --->\n"
132 | docker network connect $NETWORK_NAME_FRONTEND $ROUTER_CONTAINER_NAME
133 | printf "router added to frontend network --->\n"
134 | fi
135 | printf "starting router container --->\n"
136 | docker container start $ROUTER_CONTAINER_NAME;
137 | printf "router container started --->\n"
138 |
139 | cd ..
140 | printf "\n"
141 |
142 | printf "build script finished\n\n"
--------------------------------------------------------------------------------