1 Commits

Author SHA1 Message Date
ebf03f3e56 Modiy route or example pr 2025-06-19 21:17:48 -04:00
71 changed files with 120 additions and 109 deletions

View File

@ -1,18 +0,0 @@
{
"name": "Fullstack DevContainer",
"dockerComposeFile": ["../compose.yml", "../compose.override.yml"],
"service": "backend",
"workspaceFolder": "/app",
"shutdownAction": "stopCompose",
"forwardPorts": [80, 8080],
"postCreateCommand": "/entrypoint.sh",
"customizations": {
"vscode": {
"extensions": [
"jripouteau.adonis-vscode-extension",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
}

View File

@ -1,49 +0,0 @@
# Overview
This is a practical example of a "basic" fullstack application. This application has been fully containerized to be run locally in development mode as well as for deployment to production. It is composed of the following services:
- frontend - Basic React app bootstrapped with `yarn create vite`
- backend - AdonisJS API Backend [https://adonisjs.com]()
- database - Postgres database
- reverse_proxy - Traefik ingress controller handling reverse proxy for the frontend and backend applications.
## Getting Started
### Devcontainer
You can run this project via dev containers. First you will need to ensure that you have the [VSCode dev containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
Once you have the extension installed, there are a couple ways to get started. From this directory run `code .` to open a new VSCode window targeting this directory. After a few seconds you should get a notification in the bottom right asking if you want to reopen the workspace in a container. This will open a new VScode window and will start the process of building and running all of the containers.
<video src="./open-devcontainers-option1.m4v" controls></video>
Another method is to open the `Command Palette` (CMD+Shift+P) and search for `Dev Containers: Open Folder in Dev Container`. Select this folder and VScode will open a new window and kick off the process. The very first time you do this it will take several minutes to build the images and start the containers.
<video src="./open-devcontainers-option2.m4v" controls></video>
Alternatively, you can also just use the compose files to start a stack and connect your VSCode instance to the backend container.
To do that, clone the repo and `cd` into this directory.
```shell
cd examples/devcontainers
docker compose up
```
The first time you run `docker compose up` it may take a few minutes as Docker will need to build images for the frontend and backend. Also there are some database migrations and seed data that need to happen. Those are handled by `backend/entrypoint.sh`. This is handled for you automatically since it is baked into the image. Once everything is up you should be able to access the frontend at [http://app.docker.localhost:8888]() and the backend at [http://app.docker.localhost:8888/api]()
![](./homepage.png)
## Developing
When running the `compose` stack, the backend/frontend node_modules are isolated from your host system. This is common in dev environment setups. In this case it is advised to use a Dev container. The following video demonstrates starting a dev container by using the VSCode plugin.
<video src="../../assets/open-dev-container.m4v" controls></video>
### Proxying and routes
You will notice that the backend is listening on `http://0.0.0.0:3333/` but the frontend is making requests to `/api/`. This is designed to mimic how you would deploy something like this in production where the backend would be behind a reverse proxy. Traefik has some really nice middleware that allows us to easily tell it to route requests destined for `/api` to `/`. The bit that handles that are these labels:
```compose
labels:
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.rule=Host(`app.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.middlewares=strip-api-prefix@docker"
```
We are defining a middleware named `strip-api-prefix` using the built in `strippreffix` Traefik middleware. We are then telling it to remove `/api` from any requests it handles. We then attach that to our backend router.

View File

@ -1,8 +0,0 @@
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
export default class UsersController {
public async index({ request }: HttpContext) {
const page = request.input('page', 1)
return User.query().paginate(page)
}
}

View File

@ -1,17 +0,0 @@
---
services:
backend:
build:
context: backend
target: develop
volumes:
- ./backend:/app/backend/
- node_modules:/app/backend/node_modules
- frontend_node_modules:/app/frontend/node_modules
- ./frontend/:/app/frontend/
depends_on:
db:
condition: service_healthy
volumes:
node_modules: {}
frontend_node_modules: {}

View File

@ -0,0 +1,25 @@
{
"name": "Fullstack DevContainer",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "frontend",
"workspaceFolder": "/app",
"shutdownAction": "stopCompose",
"forwardPorts": [80, 8080],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
},
"mounts": [
"source=frontend_node_modules,target=/app/node_modules,type=volume",
"source=backend_node_modules,target=/backend/node_modules,type=volume",
"source=${localWorkspaceFolder}/backend,target=/backend,type=bind"
],
"postCreateCommand": "npm install && cd /backend && npm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
}

View File

@ -0,0 +1,37 @@
# Overview
This is a practical example of a "basic" fullstack application. This application has been fully containerized to be run locally in development mode as well as for deployment to production. It is composed of the following services:
- frontend - Basic React app bootstrapped with `yarn create vite`
- backend - AdonisJS API Backend [https://adonisjs.com]()
- database - Postgres database
- reverse_proxy - Traefik ingress controller handling reverse proxy for the frontend and backend applications.
## Getting Started
Clone the repo and `cd` into the `compose` directory.
```shell
cp backend/.env.example backend/.env
docker compose up
```
The first time you run `docker compose up` it may take a few minutes as Docker will need to build images for the frontend and backend. Also there are some database migrations and seed data that need to happen. Those are handled by `backend/dev-entrypoint.sh`. This is handled for you automatically since it is baked into the image. Once everything is up you should be able to access the frontend at [http://app.docker.localhost:8888]() and the backend at [http://app.docker.localhost:8888/api]()
![](./homepage.png)
## Developing
When running the `compose` stack, the backend node_modules are isolated from your host system. This is common in dev environment setups. In this case it is advised to use a Dev container. The following video demonstrates starting a dev container by using the VSCode plugin.
<video src="../../assets/open-dev-container.m4v" controls></video>
### Proxying and routes
You will notice that the backend is listening on `http://0.0.0.0:3333/` but the frontend is making requests to `/api/`. This is designed to mimic how you would deploy something like this in production where the backend would be behind a reverse proxy. Traefik has some really nice middleware that allows us to easily tell it to route requests destined for `/api` to `/`. The bit that handles that are these labels:
```compose
labels:
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.rule=Host(`app.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.middlewares=strip-api-prefix@docker"
```
We are defining a middleware named `strip-api-prefix` using the built in `strippreffix` Traefik middleware. We are then telling it to remove `/api` from any requests it handles. We then attach that to our backend router.

View File

@ -7,11 +7,12 @@ HEALTHCHECK --interval=5s \
# All deps stage
FROM base AS deps
WORKDIR /app/backend
WORKDIR /app
ADD package.json package-lock.json ./
RUN npm ci
FROM deps AS develop
ENV NODE_ENV=development
WORKDIR /app
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
@ -20,14 +21,14 @@ ENTRYPOINT [ "/entrypoint.sh" ]
# Production only deps stage
FROM base AS production-deps
WORKDIR /app/backend
WORKDIR /app
ADD package.json package-lock.json ./
RUN npm ci --omit=dev
# Build stage
FROM base AS build
WORKDIR /app/backend
COPY --from=deps /app/backend/node_modules /app/backend/node_modules
WORKDIR /app
COPY --from=deps /app/node_modules /app/node_modules
ADD . .
RUN node ace build
@ -35,8 +36,8 @@ RUN node ace build
FROM base
ENV NODE_ENV=production
WORKDIR /app
COPY --from=production-deps /app/backend/node_modules /app/backend/node_modules
COPY --from=build /app/backend/build /app
COPY --from=production-deps /app/node_modules /app/node_modules
COPY --from=build /app/build /app
EXPOSE 8080
CMD ["node", "./bin/server.js"]

View File

@ -0,0 +1,8 @@
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
export default class UsersController {
public async index({ request }: HttpContext) {
const page = request.input('page', 1)
return User.query().paginate(page)
}
}

View File

@ -1,7 +1,5 @@
#!/bin/sh
cd /app/backend
cp .env.example .env
echo "starting up..."
node ace generate:key

View File

@ -8,10 +8,10 @@
*/
import router from '@adonisjs/core/services/router'
const UsersController = () => import('#controllers/users_controller')
import UsersController from '#controllers/users_controller'
router.get('users', [UsersController, 'index'])
router.get('/', async () => {
return {
hello: 'world',
hello: 'SCS',
}
})

View File

@ -0,0 +1,14 @@
---
services:
backend:
build:
context: backend
target: develop
volumes:
- ./backend:/app
- node_modules:/app/node_modules
depends_on:
db:
condition: service_healthy
volumes:
node_modules: {}

View File

@ -6,8 +6,8 @@ services:
context: ./frontend
target: develop
volumes:
- frontend_node_modules:/app/frontend/node_modules
- ./frontend/:/app/frontend/
- frontend_node_modules:/app/node_modules
- ./frontend/src:/app/src
labels:
- "traefik.http.routers.app.rule=Host(`app.docker.localhost`)"
reverse-proxy:
@ -27,6 +27,8 @@ services:
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.rule=Host(`app.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.middlewares=strip-api-prefix@docker"
env_file:
- ./backend/.env
depends_on:
db:
condition: service_healthy

View File

@ -1,5 +1,5 @@
FROM node:22-alpine AS base
WORKDIR /app/frontend
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
@ -12,5 +12,5 @@ EXPOSE 5173
ENTRYPOINT [ "yarn", "dev", "--host", "0.0.0.0" ]
FROM nginx:alpine AS production
COPY --from=build /app/frontend/dist/ /usr/share/nginx/html
COPY --from=build /app/dist/ /usr/share/nginx/html
ENTRYPOINT [ "nginx", "-g", "daemon off;" ]

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -18,6 +18,21 @@
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}

View File

@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import dockerLogo from './assets/docker.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
@ -37,6 +38,9 @@ function App() {
return (
<>
<div>
<a href="https://vite.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
@ -45,7 +49,7 @@ function App() {
</a>
</div>
<h1>React + Docker + Traefik</h1>
<h1>Vite + React + Docker + Traefik</h1>
<table>
<thead>

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

@ -33,7 +33,6 @@ Mike Conrad - SCS 2025
<p><a href="https://hackanooga.com/scs">https://hackanooga.com/scs</a></p>
<small>Includes slide deck, and repo with examples</small>
<img src="./images/talk.jpg" width="200">
Connect with me on LinkedIn: https://linkedin.com/enxoco
</div>
<div class="abs-br m-6 text-xl">