diff --git a/README.md b/README.md index 547880b..1ac77b8 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ## The Ultimate Go and React Development Setup with Docker (Part 2) -### Building A Complete Example +### Building A Complete API Example -This repository is paired with a [blog post](https://blog.ivorscott.com/ultimate-go-react-development-setup-with-docker-part2). If you follow along, the project starter is available under the `part2_starter` branch. Part 2 is heavily influenced by [Ardan labs service training](https://github.com/ardanlabs/service-training). I highly recommend their [courses](https://education.ardanlabs.com/). +This repository is paired with a [blog post](https://blog.ivorscott.com/ultimate-go-react-development-setup-with-docker-part2). Part 2 is heavily influenced by [Ardan labs service training](https://github.com/ardanlabs/service-training). I highly recommend their [courses](https://education.ardanlabs.com/). [Previous blog post](https://blog.ivorscott.com/ultimate-go-react-development-setup-with-docker) @@ -445,6 +445,10 @@ make debug-api # use delve on the same api in a separate container (no live relo make debug-db # use pgcli to inspect postgres db +make rm # remove all containers + +make rmi # remove all images + make exec user="..." service="..." cmd="..." # execute command in running container make tidy # clean up unused api dependencies @@ -453,6 +457,8 @@ make test-api # run api tests make test-client # run client tests +make test-client-watch # run client tests and watch + make migration # create a migration make version # print current migration version @@ -519,7 +525,7 @@ Run the debuggable api. Set a break point on a route handler. Click 'Launch remo -### Building A Complete Example +### Building A Complete API Example The Ultimate Go and React Development Setup with Docker (Part 2) diff --git a/client/Dockerfile b/client/Dockerfile index 339d19d..f7ce78c 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -7,74 +7,69 @@ WORKDIR /client # 3. Copy both package.json and package-lock.json into /client in the image's filesystem COPY package*.json ./ -# 4. Install only the production node_modules and clean up the cache -RUN npm ci \ - && npm cache clean --force - -# 5. Extend the base stage and create a new stage named dev +# 4. Extend the base stage and create a new stage named dev FROM base as dev -# 6. Set the NODE_ENV and PATH Environment variables +# 5. Set the NODE_ENV and PATH Environment variables ENV NODE_ENV=development ENV PATH /client/node_modules/.bin:$PATH -# 7. Provide meta data about the port the container must expose +# 6. Provide meta data about the port the container must expose EXPOSE 3000 -# 8. Create a new /app directory in /client +# 7. Create a new /app directory in /client RUN mkdir /client/app -# 9. Install development dependencies -RUN npm i --only=development \ - && npm cache clean --force +# 8. Install development dependencies +RUN npm install && npm cache clean --force -# 10. Print npm configuration for debugging purposes +# 9. Print npm configuration for debugging purposes RUN npm config list -# 11. Set the working directory to /client/app +# 10. Set the working directory to /client/app WORKDIR /client/app -# 12. Provide the default command for the development container +# 11. Provide the default command for the development container CMD ["npm", "run", "start"] -# 13. Extend the dev stage and create a new stage called test +# 12. Extend the dev stage and create a new stage called test FROM dev as test -# 14. Copy the remainder of the client folder source code into the image's filesystem +# 13. Copy the remainder of the client folder source code into the image's filesystem COPY . . -# 15. Run node_module vulnerability checks +# 14. Run node_module vulnerability checks RUN npm audit -# 16. Run unit tests in CI +# 15. Run unit tests in CI RUN CI=true npm test --env=jsdom -# 17. Extend the test stage and create a new stage named build-stage +# 16. Extend the test stage and create a new stage named build-stage FROM test as build-stage -# 18. Build the production static assets +# 17. Build the production static assets RUN npm run build -# 19. Install aquasecurity's trivy for robust image scanning -FROM aquasec/trivy:0.4.3 as trivy +# 18. Install aquasecurity's trivy for robust image scanning +FROM aquasec/trivy:0.4.4 as trivy -# 20. Scan the nginx alpine image before production use +# 19. Scan the nginx alpine image before production use RUN trivy nginx:1.17-alpine && \ echo "No image vulnerabilities" > result -# 21. Extend the nginx apline image and create a new stage named prod +# 20. Extend the nginx apline image and create a new stage named prod FROM nginx:1.17-alpine as prod -# 22. Copy only the files we want from a few stages into the prod stage +# 21. Copy only the files we want from a few stages into the prod stage COPY --from=trivy result secure COPY --from=build-stage /client/app/build /usr/share/nginx/html -# 23. Copy non-root user nginx configuration +# 22. Copy non-root user nginx configuration # https://hub.docker.com/_/nginx COPY --from=build-stage /client/app/nginx /etc/nginx/ -# 24. Provide meta data about the port the container must expose +# 23. Provide meta data about the port the container must expose EXPOSE 80 -# 25. Define how Docker should test the container to check that it is still working +# 24. Define how Docker should test the container to check that it is still working HEALTHCHECK CMD [ "wget", "-q", "0.0.0.0:80" ] \ No newline at end of file diff --git a/client/package.json b/client/package.json index 589f6f6..c242b30 100644 --- a/client/package.json +++ b/client/package.json @@ -68,7 +68,7 @@ "workbox-webpack-plugin": "4.3.1" }, "scripts": { - "start": "node scripts/start.js", + "start": "HTTPS=true REACT_APP_API_URL=https://localhost:4000/v1 node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" }, diff --git a/client/src/App/App.scss b/client/src/App/App.scss index c56f345..31830d5 100644 --- a/client/src/App/App.scss +++ b/client/src/App/App.scss @@ -10,9 +10,7 @@ body { box-sizing: border-box; // https://css-tricks.com/snippets/css/font-shorthand/ font: normal 400 24px/1.7 "Lato", Helvetica, sans-serif; - background: #000; color: #777; - padding: 30px; } .header { @@ -20,8 +18,8 @@ body { height: 85vh; background-image: linear-gradient( to right bottom, - rgba(126, 2123, 111, 0.8), - rgba(209, 73, 31, 0.6) + rgba(126, 212, 111, 0.8), + rgba(209, 73, 31, 0.8) ), url("http://getwallpapers.com/wallpaper/full/d/3/a/547521.jpg"); background-size: cover; @@ -133,54 +131,13 @@ body { } } -.products-feature { +.main { position: relative; - top: -650px; - margin: 100px; + top: -500px; + text-align: center; color: #fff; - - header { - font-size: 36px; - font-weight: 700; - text-align: center; - margin-bottom: 46px; - } - - .products { - display: flex; - flex: 1; - justify-content: center; - &__card { - width: 100%; - min-height: 300px; - max-width: 300px; - margin: 10px; - padding: 12px; - background-color: #fff; - border-radius: 8px; - color: #444; - box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), - 0 10px 10px rgba(0, 0, 0, 0.22); - } - .a { - position: relative; - top: 0px; - animation: moveInBottom 0.65s ease-out 1s; - animation-fill-mode: backwards; - } - .b { - position: relative; - top: 40px; - animation: moveInBottom 0.65s ease-out 1.2s; - animation-fill-mode: backwards; - } - .c { - position: relative; - top: 80px; - animation: moveInBottom 0.65s ease-out 1.4s; - animation-fill-mode: backwards; - } - } + animation: moveInBottom 0.65s ease-out 1.4s; + animation-fill-mode: backwards; } @keyframes moveInLeft { diff --git a/client/src/App/App.test.tsx b/client/src/App/App.test.tsx index 4db7ebc..8cf3f51 100644 --- a/client/src/App/App.test.tsx +++ b/client/src/App/App.test.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; +import React from "react"; +import { render } from "@testing-library/react"; +import App from "./App"; -test('renders learn react link', () => { +test("renders learn react link", () => { const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); + const button = getByText(/Sign Up/i); + expect(button).toBeInTheDocument(); }); diff --git a/client/src/App/App.tsx b/client/src/App/App.tsx index 4b3f9b2..5dce039 100644 --- a/client/src/App/App.tsx +++ b/client/src/App/App.tsx @@ -27,7 +27,11 @@ class App extends React.Component<{}, State> { subtitle="Second hand games" callToActionText="Sign Up" /> - +
+

The Best Prices

+

Gaming Is Our Passion. Get 20% Off Any 2nd Purchase!

+ +
); } diff --git a/client/src/App/Products/Products.scss b/client/src/App/Products/Products.scss new file mode 100644 index 0000000..54edd32 --- /dev/null +++ b/client/src/App/Products/Products.scss @@ -0,0 +1,26 @@ +.products-feature { + color: #fff; + margin-top: 40px; + + .products { + display: flex; + flex: 1; + justify-content: center; + &__card { + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + width: 100%; + min-height: 300px; + max-width: 300px; + margin: 10px; + padding: 12px; + background-color: #fff; + border-radius: 8px; + color: #444; + box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), + 0 10px 10px rgba(0, 0, 0, 0.22); + } + } +} diff --git a/client/src/App/Products/Products.tsx b/client/src/App/Products/Products.tsx index efd380b..f5b3b66 100644 --- a/client/src/App/Products/Products.tsx +++ b/client/src/App/Products/Products.tsx @@ -1,26 +1,16 @@ import React from "react"; import { IProduct } from "./types"; - -const classes = ["a", "b", "c"]; +import "./Products.scss"; const Products: React.FC<{ products: IProduct[] }> = ({ products }) => (
-
Store
- {products.map((product, index) => { + {products.map(product => { return ( - index < 3 && ( -
- {product.name} -

- {product.description} -

-

{product.price}

-
- ) +
+ {product.name} +

{product.price}

+
); })}
diff --git a/docker-compose.yml b/docker-compose.yml index cbb0c78..fcf94bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: - ./api:/api networks: - postgres-net + # swap commands to disable live reload # command: go run ./cmd/api command: CompileDaemon --build="go build -o main ./cmd/api" -log-prefix=false --command=./main @@ -33,9 +34,13 @@ services: environment: HTTPS: "true" REACT_APP_API_URL: https://localhost:$API_PORT/v1 + CHOKIDAR_USEPOLLING: "true" ports: - $CLIENT_PORT:$CLIENT_PORT + # fixes bug in react-scripts/start.js found in latest create react app release + # https://github.com/facebook/create-react-app/issues/8688 tty: true + stdin_open: true volumes: - ./client:/client/app - /client/app/node_modules @@ -63,7 +68,7 @@ services: networks: - postgres-net # run a container without the default seccomp profile - # https://github.com/go-delve/delve/issues/515 + # https://github.com/go-delve/delve/issues/515#issuecomment-214911481 security_opt: - "seccomp:unconfined" tty: true diff --git a/makefile b/makefile index 00266cd..6d90bc1 100644 --- a/makefile +++ b/makefile @@ -100,7 +100,13 @@ exec: test-client: @echo [ running client tests ... ] - @docker-compose run client npm test + @cd client; npx jest --notify + @cd .. + +test-client-watch: + @echo [ running client tests ... ] + @cd client; npx jest --watchAll + @cd .. test-api: @echo [ running api tests ... ] @@ -118,6 +124,14 @@ debug-db: @# includes auto-completion and syntax highlighting. https://www.pgcli.com/ @docker run -it --rm --net $(POSTGRES_NET) dencold/pgcli $(URL) +rm: + @echo [ removing all containers ... ] + docker rm -f `docker ps -aq` + +rmi: + @echo [ removing all images ... ] + docker rmi -f `docker images -a -q` + migration: ifndef name $(error migration name is missing -> make migration ) @@ -214,6 +228,8 @@ insert: .PHONY: db .PHONY: debug-api .PHONY: debug-db +.PHONY: rm +.PHONY: rmi .PHONY: down .PHONY: dump .PHONY: force