Repository: DanWahlin/NodeExpressMongoDBDockerApp
Branch: main
Commit: 170d42625853
Files: 27
Total size: 31.8 KB
Directory structure:
gitextract_08x7bqud/
├── .dockerignore
├── .gitignore
├── .vscode/
│ └── settings.json
├── README.md
├── config/
│ ├── config.development.json
│ └── config.production.json
├── dbSeeder.js
├── docker-compose.yml
├── lib/
│ ├── configLoader.js
│ ├── dataSeeder.js
│ ├── database.js
│ ├── dockerCommandsRepository.js
│ └── logger.js
├── logs/
│ └── .gitkeep
├── models/
│ └── dockerCommand.js
├── nginx/
│ └── index.html
├── node.dockerfile
├── package.json
├── public/
│ └── styles/
│ ├── styles.css
│ └── tailwind.css
├── routes/
│ └── index.js
├── server.js
├── tailwind.config.js
└── views/
├── error.hbs
├── index.hbs
├── layouts/
│ └── masterLayout.hbs
└── newcommand.hbs
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
node_modules
================================================
FILE: .gitignore
================================================
node_modules/
.DS_Store
.certs/
.idea/
.settings/
.build/
logs/access.log
*.iml
================================================
FILE: .vscode/settings.json
================================================
{
"gitdoc.enabled": false
}
================================================
FILE: README.md
================================================
# Node.js with MongoDB and Docker Demo
Application demo designed to show how Node.js and MongoDB can be run in Docker containers.
The app uses Mongoose to create a simple database that stores Docker commands and examples.
Interested in learning more about Docker? Visit https://www.pluralsight.com/courses/docker-web-development to view my Docker for Web Developers course.
### Starting the Application with Docker Containers:
1. Install [Docker Desktop](https://docker.com/get-started)
2. Open a command prompt.
3. Run the commands listed in `node.dockerfile` (see the comments at the top of the file).
4. Navigate to http://localhost:3000.
### Starting the Application with Docker Compose
1. Install [Docker Desktop](https://docker.com/get-started)
2. Open a command prompt at the root of the application's folder.
3. Run `docker compose build`
4. Run `docker compose up`
5. Open another command prompt and run `docker compose ps -a` and note the ID of the Node container
6. Run `docker exec -it <nodeContainerID> sh` (replace <nodeContainerID> with the proper ID) to sh into the container
7. Run `node dbSeeder.js` to seed the MongoDB database
8. Type `exit` to leave the sh session
9. Navigate to http://localhost:3000 in your browser to view the site. This assumes that's the IP assigned to VirtualBox - change if needed.
10. Run `docker-compose down` to stop the containers and remove them.
## To run the app with Node.js and MongoDB (without Docker):
1. Install and start MongoDB (https://docs.mongodb.com/manual/administration/install-community/).
2. Install the LTS version of Node.js (http://nodejs.org).
3. Open `config/config.development.json` and adjust the host name to your MongoDB server name (`localhost` normally works if you're running locally).
4. Run `npm install`.
5. Run `node dbSeeder.js` to get the sample data loaded into MongoDB. Exit the command prompt.
6. Run `npm start` to start the server.
7. Navigate to http://localhost:3000 in your browser.
================================================
FILE: config/config.development.json
================================================
{
"databaseConfig": {
"host": "mongodb",
"database": "funWithDocker"
}
}
================================================
FILE: config/config.production.json
================================================
{
"databaseConfig": {
"host": "mongodb",
"database": "funWithDocker"
}
}
================================================
FILE: dbSeeder.js
================================================
import dataInitializer from './lib/dataSeeder.js';
import config from './config/config.development.json' with { type: 'json' };
import db from './lib/database.js';
db.init(config.databaseConfig);
console.log('Initializing Data');
dataInitializer.initializeData(function(err) {
if (err) {
console.log(err);
}
else {
console.log('Data Initialized!')
}
});
================================================
FILE: docker-compose.yml
================================================
services:
node:
container_name: nodeapp
image: nodeapp
build:
context: .
dockerfile: node.dockerfile
args:
PACKAGES: "nano wget curl"
ports:
- "3000:3000"
networks:
- nodeapp-network
volumes:
- ./logs:/var/www/logs
environment:
- NODE_ENV=production
- APP_VERSION=1.0
depends_on:
- mongodb
# restart: on-failure
# deploy:
# replicas: 5
mongodb:
container_name: mongodb
image: mongo
networks:
- nodeapp-network
networks:
nodeapp-network:
driver: bridge
================================================
FILE: lib/configLoader.js
================================================
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import path from 'path';
import logger from './logger.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let env = process.env.NODE_ENV;
if (!env) {
env = 'development';
}
logger.log('Node environment: ' + env);
logger.log('loading config.' + env + '.json');
const configPath = path.join(__dirname, '..', 'config', `config.${env}.json`);
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
export default config;
================================================
FILE: lib/dataSeeder.js
================================================
import DockerCommand from '../models/dockerCommand.js';
const dataInitializer = {
initializeData: async function(callback) {
const runDockerCommand = new DockerCommand({
command: 'run',
description: 'Runs a Docker container',
examples: [
{
example: 'docker run imageName',
description: 'Creates a running container from the image. Pulls it from Docker Hub if the image is not local'
},
{
example: 'docker run -d -p 8080:3000 imageName',
description: 'Runs a container in "detached" mode with an external port of 8080 and a container port of 3000.'
}
]
});
const psDockerCommand = new DockerCommand({
command: 'ps',
description: 'Lists containers',
examples: [
{
example: 'docker ps',
description: 'Lists all running containers'
},
{
example: 'docker ps -a',
description: 'Lists all containers (even if they are not running)'
}
]
});
try {
await runDockerCommand.save();
await psDockerCommand.save();
callback();
} catch (err) {
callback(err);
}
}
};
export default dataInitializer;
================================================
FILE: lib/database.js
================================================
import mongoose from 'mongoose';
const database = {
conn: null,
init: function(config) {
console.log('Trying to connect to ' + config.host + '/' + config.database + ' MongoDB database');
const connString = `mongodb://${config.host}/${config.database}`;
mongoose.connect(connString);
this.conn = mongoose.connection;
this.conn.on('error', console.error.bind(console, 'connection error:'));
this.conn.once('open', () => console.log('db connection open'));
return this.conn;
},
close: function() {
if (this.conn) {
this.conn.close(() => {
console.log('Mongoose default connection disconnected through app termination');
process.exit(0);
});
}
}
};
export default database;
================================================
FILE: lib/dockerCommandsRepository.js
================================================
import DockerCommand from '../models/dockerCommand.js';
const getDockerCommands = async () => {
try {
const commands = await DockerCommand.find();
return commands;
}
catch (err) {
return [];
}
};
export default getDockerCommands;
================================================
FILE: lib/logger.js
================================================
const logger = {
log: function(msg) {
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
console.log(msg);
}
}
};
export default logger;
================================================
FILE: logs/.gitkeep
================================================
================================================
FILE: models/dockerCommand.js
================================================
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
const exampleSchema = new Schema({
example: { type: String, required: true },
description: { type: String, required: true },
});
const dockerCommandSchema = new Schema({
command: { type: String, required: true },
description: { type: String, required: true },
examples: [exampleSchema]
});
const DockerCommandModel = mongoose.model('dockerCommand', dockerCommandSchema);
export default DockerCommandModel;
================================================
FILE: nginx/index.html
================================================
<html>
<body>
Hello from a file stored in a volume!
</body>
</html>
================================================
FILE: node.dockerfile
================================================
FROM node:alpine
LABEL author="Dan Wahlin"
ARG PACKAGES=nano
ENV NODE_ENV=production
ENV PORT=3000
ENV TERM=xterm
RUN apk update && apk add --no-cache $PACKAGES
WORKDIR /var/www
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . ./
EXPOSE $PORT
ENTRYPOINT ["npm", "start"]
# Build: docker build -f node.dockerfile -t nodeapp .
# Option 1: Create a custom bridge network and add containers into it
## Create a user-defined bridge network and add containers
# docker network create --driver bridge isolated_network
# docker run -d --network=isolated_network --name mongodb mongo
## NOTE: Use $(pwd) on macOS/Linux, ${PWD} in PowerShell, and %cd% in Windows CMD.
# macOS/Linux
# docker run -d --network=isolated_network --name nodeapp -p 3000:3000 -v "$(pwd)"/logs:/var/www/logs nodeapp
# PowerShell
# docker run -d --network=isolated_network --name nodeapp -p 3000:3000 -v ${PWD}/logs:/var/www/logs nodeapp
# Option 2 (Legacy Linking - this is the OLD way)
# Start MongoDB and Node (link Node to MongoDB container with legacy linking)
# docker run -d --name my-mongodb mongo
# docker run -d -p 3000:3000 --link my-mongodb:mongodb --name nodeapp danwahlin/nodeapp
================================================
FILE: package.json
================================================
{
"name": "expressite",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node server.js",
"tailwind:css": "tailwindcss -i public/styles/tailwind.css -o public/styles/styles.css"
},
"dependencies": {
"@handlebars/allow-prototype-access": "^1.0.5",
"cookie-parser": "^1.4.7",
"debug": "^4.4.3",
"express": "5.1.0",
"express-handlebars": "8.0.3",
"handlebars": "^4.7.8",
"mongoose": "^8.19.2",
"morgan": "^1.10.1",
"readline": "1.3.0",
"serve-favicon": "^2.5.1"
},
"devDependencies": {
"@tailwindcss/cli": "^4.1.16",
"tailwindcss": "4.1.16"
}
}
================================================
FILE: public/styles/styles.css
================================================
/*! tailwindcss v4.1.14 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--color-blue-500: oklch(62.3% 0.214 259.815);
--color-gray-300: oklch(87.2% 0.01 258.338);
--color-gray-400: oklch(70.7% 0.022 261.325);
--color-gray-700: oklch(37.3% 0.034 259.733);
--color-gray-800: oklch(27.8% 0.033 256.848);
--color-gray-900: oklch(21% 0.034 264.665);
--color-white: #fff;
--spacing: 0.25rem;
--container-7xl: 80rem;
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--text-base: 1rem;
--text-base--line-height: calc(1.5 / 1);
--text-lg: 1.125rem;
--text-lg--line-height: calc(1.75 / 1.125);
--font-weight-medium: 500;
--font-weight-bold: 700;
--radius-md: 0.375rem;
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
font-feature-settings: var(--default-mono-font-feature-settings, normal);
font-variation-settings: var(--default-mono-font-variation-settings, normal);
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
::-webkit-calendar-picker-indicator {
line-height: 1;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden="until-found"])) {
display: none !important;
}
}
@layer utilities {
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
}
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 64rem) {
max-width: 64rem;
}
@media (width >= 80rem) {
max-width: 80rem;
}
@media (width >= 96rem) {
max-width: 96rem;
}
}
.mx-auto {
margin-inline: auto;
}
.-mr-2 {
margin-right: calc(var(--spacing) * -2);
}
.mb-2 {
margin-bottom: calc(var(--spacing) * 2);
}
.mb-6 {
margin-bottom: calc(var(--spacing) * 6);
}
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.ml-10 {
margin-left: calc(var(--spacing) * 10);
}
.block {
display: block;
}
.flex {
display: flex;
}
.hidden {
display: none;
}
.inline-flex {
display: inline-flex;
}
.h-6 {
height: calc(var(--spacing) * 6);
}
.h-9 {
height: calc(var(--spacing) * 9);
}
.h-10 {
height: calc(var(--spacing) * 10);
}
.h-16 {
height: calc(var(--spacing) * 16);
}
.w-6 {
width: calc(var(--spacing) * 6);
}
.w-10 {
width: calc(var(--spacing) * 10);
}
.w-14 {
width: calc(var(--spacing) * 14);
}
.w-full {
width: 100%;
}
.max-w-7xl {
max-width: var(--container-7xl);
}
.flex-shrink-0 {
flex-shrink: 0;
}
.flex-col {
flex-direction: column;
}
.items-baseline {
align-items: baseline;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.space-y-1 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-4 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-x-4 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
}
}
.rounded {
border-radius: 0.25rem;
}
.rounded-md {
border-radius: var(--radius-md);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.border-gray-300 {
border-color: var(--color-gray-300);
}
.bg-gray-800 {
background-color: var(--color-gray-800);
}
.bg-gray-900 {
background-color: var(--color-gray-900);
}
.bg-white {
background-color: var(--color-white);
}
.p-2 {
padding: calc(var(--spacing) * 2);
}
.px-2 {
padding-inline: calc(var(--spacing) * 2);
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
}
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
.py-4 {
padding-block: calc(var(--spacing) * 4);
}
.py-6 {
padding-block: calc(var(--spacing) * 6);
}
.pt-2 {
padding-top: calc(var(--spacing) * 2);
}
.pb-3 {
padding-bottom: calc(var(--spacing) * 3);
}
.font-sans {
font-family: var(--font-sans);
}
.text-base {
font-size: var(--text-base);
line-height: var(--tw-leading, var(--text-base--line-height));
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.text-gray-300 {
color: var(--color-gray-300);
}
.text-gray-400 {
color: var(--color-gray-400);
}
.text-gray-900 {
color: var(--color-gray-900);
}
.text-white {
color: var(--color-white);
}
.no-underline {
text-decoration-line: none;
}
.shadow {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-sm {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.hover\:bg-gray-700 {
&:hover {
@media (hover: hover) {
background-color: var(--color-gray-700);
}
}
}
.hover\:text-white {
&:hover {
@media (hover: hover) {
color: var(--color-white);
}
}
}
.focus\:border-blue-500 {
&:focus {
border-color: var(--color-blue-500);
}
}
.focus\:ring-2 {
&:focus {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
.focus\:ring-blue-500 {
&:focus {
--tw-ring-color: var(--color-blue-500);
}
}
.focus\:ring-white {
&:focus {
--tw-ring-color: var(--color-white);
}
}
.focus\:ring-offset-2 {
&:focus {
--tw-ring-offset-width: 2px;
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
}
}
.focus\:ring-offset-gray-800 {
&:focus {
--tw-ring-offset-color: var(--color-gray-800);
}
}
.focus\:outline-none {
&:focus {
--tw-outline-style: none;
outline-style: none;
}
}
.sm\:px-3 {
@media (width >= 40rem) {
padding-inline: calc(var(--spacing) * 3);
}
}
.sm\:px-6 {
@media (width >= 40rem) {
padding-inline: calc(var(--spacing) * 6);
}
}
.md\:block {
@media (width >= 48rem) {
display: block;
}
}
.md\:hidden {
@media (width >= 48rem) {
display: none;
}
}
.lg\:px-8 {
@media (width >= 64rem) {
padding-inline: calc(var(--spacing) * 8);
}
}
}
@property --tw-space-y-reverse {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-space-x-reverse {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: "*";
inherits: false;
}
@property --tw-ring-offset-width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --tw-ring-offset-color {
syntax: "*";
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-space-y-reverse: 0;
--tw-space-x-reverse: 0;
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-shadow: 0 0 #0000;
--tw-shadow-color: initial;
--tw-shadow-alpha: 100%;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-shadow-color: initial;
--tw-inset-shadow-alpha: 100%;
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
--tw-inset-ring-color: initial;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-inset: initial;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
}
}
}
================================================
FILE: public/styles/tailwind.css
================================================
@import "tailwindcss";
================================================
FILE: routes/index.js
================================================
import express from 'express';
import getDockerCommands from '../lib/dockerCommandsRepository.js';
import DockerCommandModel from '../models/dockerCommand.js';
const router = express.Router();
/* GET home page. */
router.get('/', async (req, res, next) => {
const commands = await getDockerCommands();
res.render('index', { dockerCommands: commands });
});
/* GET new command page */
router.get('/newcommand', (req, res, next) => {
res.render('newcommand');
});
router.post('/newcommand', async (req, res, next) => {
// Extremely simple implementation to get a command in the database
const commandData = {
command: req.body.command,
description: req.body.description,
examples: [{
example: req.body.example,
description: req.body.ex_description
}]
}
const command = new DockerCommandModel(commandData);
try {
const cmd = await command.save();
console.log(cmd.command + " saved to commands collection.");
}
catch (err) {
console.log(err);
}
res.redirect('/');
});
export default router;
================================================
FILE: server.js
================================================
import express from 'express';
import exphbs from 'express-handlebars';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import favicon from 'serve-favicon';
import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import Handlebars from 'handlebars';
import { allowInsecurePrototypeAccess } from '@handlebars/allow-prototype-access';
import config from './lib/configLoader.js';
import db from './lib/database.js';
import routes from './routes/index.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const port = process.env.PORT || 3000;
const app = express();
// view engine setup
const hbs = exphbs.create({
extname: '.hbs',
defaultLayout: 'masterLayout',
// https://www.npmjs.com/package/@handlebars/allow-prototype-access
// Need to add due to security change in Handlebars 4.6+
handlebars: allowInsecurePrototypeAccess(Handlebars)
});
app.engine('hbs', hbs.engine);
app.set('view engine', 'hbs');
app.set('views', path.join(__dirname, 'views'));
// create a write stream (in append mode)
const accessLogStream = fs.createWriteStream(path.join(__dirname, '/logs/access.log'), { flags: 'a' });
app.use(morgan('combined', { stream: accessLogStream }));
app.use(favicon(__dirname + '/public/images/favicon.ico'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
//Pass database config settings
db.init(config.databaseConfig);
app.use('/', routes);
// catch 404 and forward to error handler
app.use((req, res, next) => {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
app.listen(port, (err) => {
console.log('[%s] Listening on http://localhost:%d', app.settings.env, port);
});
//*********************************************************
// Quick and dirty way to detect event loop blocking
//*********************************************************
let lastLoop = Date.now();
function monitorEventLoop() {
const time = Date.now();
if (time - lastLoop > 1000) console.error('Event loop blocked ' + (time - lastLoop));
lastLoop = time;
setTimeout(monitorEventLoop, 200);
}
if (process.env.NODE_ENV === 'development') {
monitorEventLoop();
}
export default app;
================================================
FILE: tailwind.config.js
================================================
export default {
content: [
"./views/**/*.{hbs,handlebars,html}",
"./routes/**/*.js",
"./lib/**/*.js",
"./public/scripts/**/*.js"
],
theme: {
extend: {}
},
plugins: []
};
================================================
FILE: views/error.hbs
================================================
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
================================================
FILE: views/index.hbs
================================================
{{#each dockerCommands}}
<div class="flex items-center">
<div class="w-14"><img src="/images/gear-set-blue.png" class="h-9"></div>
<div class=""><h2>{{ command }} Command</h2></div>
</div>
<br />
{{ description }}
<br /><br />
{{#each examples}}
<h3>{{ example }}</h3>
{{ description }}
<br />
{{/each}}
<br><br>
{{/each}}
================================================
FILE: views/layouts/masterLayout.hbs
================================================
<!DOCTYPE html>
<html class="font-sans">
<head>
<title>{{title}}</title>
<link href="/styles/styles.css" rel="stylesheet">
<link rel="icon" href="images/favicon.ico">
</head>
<body>
<!-- This example requires Tailwind CSS v2.0+ -->
<div>
<nav class="bg-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<div class="flex-shrink-0">
<img class="h-10 w-10" src="/images/docker.png" alt="Docker">
</div>
<a href="/" class="text-white hover:bg-gray-700 hover:text-white ml-2 no-underline">
Building and Running Your First Docker App
</a>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="/newcommand" class="bg-gray-900 text-white px-3 py-2 rounded-md text-sm font-medium no-underline">Add Command</a>
</div>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button type="button"
class="bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
aria-controls="mobile-menu" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<!--
Heroicon name: outline/menu
Menu open: "hidden", Menu closed: "block"
-->
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<!--
Heroicon name: outline/x
Menu open: "block", Menu closed: "hidden"
-->
<svg class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="md:hidden" id="mobile-menu">
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<a href="#" class="bg-gray-900 text-white block px-3 py-2 rounded-md text-base font-medium">Add Command</a>
</div>
</div>
</nav>
<header class="bg-white shadow">
<div class="max-w-7xl mx-auto py-2 px-4 sm:px-6 lg:px-8">
<h1 class="text-gray-900">
Docker Commands
</h1>
</div>
</header>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
{{{body}}}
</main>
</div>
</body>
</html>
================================================
FILE: views/newcommand.hbs
================================================
<form class="flex flex-col space-y-4 mb-6" method="post">
<div class="flex flex-col">
<label class="mb-2 font-bold text-lg" for="command">Command</label>
<input class="block w-full py-2 px-3 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" type="text" name="command" id="command">
</div>
<div class="flex flex-col">
<label class="mb-2 font-bold text-lg" for="description">Description</label>
<textarea class="block w-full py-4 px-3 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" name="description" id="description"></textarea>
</div>
<div class="flex flex-col">
<label class="mb-2 font-bold text-lg" for="example">Example</label>
<input class="block w-full py-2 px-3 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" type="text" name="example" id="example">
</div>
<div class="flex flex-col">
<label class="mb-2 font-bold text-lg" for="ex_description">Example Description</label>
<input class="block w-full py-2 px-3 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" type="text" name="ex_description" id="ex_description">
</div>
<button type="submit" class="self-start inline-flex items-center px-4 py-2 text-lg font-semibold text-white bg-gray-800 rounded shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900">Add Command</button>
</form>
gitextract_08x7bqud/
├── .dockerignore
├── .gitignore
├── .vscode/
│ └── settings.json
├── README.md
├── config/
│ ├── config.development.json
│ └── config.production.json
├── dbSeeder.js
├── docker-compose.yml
├── lib/
│ ├── configLoader.js
│ ├── dataSeeder.js
│ ├── database.js
│ ├── dockerCommandsRepository.js
│ └── logger.js
├── logs/
│ └── .gitkeep
├── models/
│ └── dockerCommand.js
├── nginx/
│ └── index.html
├── node.dockerfile
├── package.json
├── public/
│ └── styles/
│ ├── styles.css
│ └── tailwind.css
├── routes/
│ └── index.js
├── server.js
├── tailwind.config.js
└── views/
├── error.hbs
├── index.hbs
├── layouts/
│ └── masterLayout.hbs
└── newcommand.hbs
SYMBOL INDEX (1 symbols across 1 files)
FILE: server.js
function monitorEventLoop (line 89) | function monitorEventLoop() {
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
{
"path": ".dockerignore",
"chars": 12,
"preview": "node_modules"
},
{
"path": ".gitignore",
"chars": 82,
"preview": "node_modules/\n\n.DS_Store\n.certs/\n.idea/\n.settings/\n.build/\nlogs/access.log\n\n*.iml\n"
},
{
"path": ".vscode/settings.json",
"chars": 31,
"preview": "{\n \"gitdoc.enabled\": false\n}"
},
{
"path": "README.md",
"chars": 2010,
"preview": "# Node.js with MongoDB and Docker Demo\n\nApplication demo designed to show how Node.js and MongoDB can be run in Docker c"
},
{
"path": "config/config.development.json",
"chars": 101,
"preview": "{\r\n \"databaseConfig\": {\r\n \"host\": \"mongodb\",\r\n \"database\": \"funWithDocker\"\r\n }\r\n}"
},
{
"path": "config/config.production.json",
"chars": 101,
"preview": "{\r\n \"databaseConfig\": {\r\n \"host\": \"mongodb\",\r\n \"database\": \"funWithDocker\"\r\n }\r\n}"
},
{
"path": "dbSeeder.js",
"chars": 389,
"preview": "import dataInitializer from './lib/dataSeeder.js';\r\nimport config from './config/config.development.json' with { type: '"
},
{
"path": "docker-compose.yml",
"chars": 597,
"preview": "services:\n\n node:\n container_name: nodeapp\n image: nodeapp\n build:\n context: .\n dockerfile: node.doc"
},
{
"path": "lib/configLoader.js",
"chars": 572,
"preview": "import { readFileSync } from 'fs';\r\nimport { fileURLToPath } from 'url';\r\nimport path from 'path';\r\nimport logger from '"
},
{
"path": "lib/dataSeeder.js",
"chars": 1277,
"preview": "import DockerCommand from '../models/dockerCommand.js';\r\n\r\nconst dataInitializer = {\r\n initializeData: async function(c"
},
{
"path": "lib/database.js",
"chars": 774,
"preview": "import mongoose from 'mongoose';\r\n\r\nconst database = {\r\n conn: null,\r\n\r\n init: function(config) {\r\n console.log('Tr"
},
{
"path": "lib/dockerCommandsRepository.js",
"chars": 263,
"preview": "import DockerCommand from '../models/dockerCommand.js';\r\n\r\nconst getDockerCommands = async () => {\r\n try {\r\n const c"
},
{
"path": "lib/logger.js",
"chars": 183,
"preview": "const logger = {\r\n log: function(msg) {\r\n if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {\r\n "
},
{
"path": "logs/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "models/dockerCommand.js",
"chars": 547,
"preview": "import mongoose from 'mongoose';\r\n\r\nconst Schema = mongoose.Schema;\r\nconst ObjectId = Schema.Types.ObjectId;\r\n \r\ncons"
},
{
"path": "nginx/index.html",
"chars": 83,
"preview": "<html>\n <body>\n Hello from a file stored in a volume!\n </body>\n</html>"
},
{
"path": "node.dockerfile",
"chars": 1296,
"preview": "FROM node:alpine\n\nLABEL author=\"Dan Wahlin\"\nARG PACKAGES=nano\n\nENV NODE_ENV=production\nENV "
},
{
"path": "package.json",
"chars": 647,
"preview": "{\n \"name\": \"expressite\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"start\": \"node"
},
{
"path": "public/styles/styles.css",
"chars": 14287,
"preview": "/*! tailwindcss v4.1.14 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, ut"
},
{
"path": "public/styles/tailwind.css",
"chars": 22,
"preview": "@import \"tailwindcss\";"
},
{
"path": "routes/index.js",
"chars": 1056,
"preview": "import express from 'express';\nimport getDockerCommands from '../lib/dockerCommandsRepository.js';\nimport DockerCommandM"
},
{
"path": "server.js",
"chars": 2833,
"preview": "import express from 'express';\nimport exphbs from 'express-handlebars';\nimport fs from 'fs';\nimport path from 'path';\nim"
},
{
"path": "tailwind.config.js",
"chars": 200,
"preview": "export default {\n content: [\n \"./views/**/*.{hbs,handlebars,html}\",\n \"./routes/**/*.js\",\n \"./lib/**/*.js\",\n "
},
{
"path": "views/error.hbs",
"chars": 74,
"preview": "<h1>{{message}}</h1>\n<h2>{{error.status}}</h2>\n<pre>{{error.stack}}</pre>\n"
},
{
"path": "views/index.hbs",
"chars": 391,
"preview": "{{#each dockerCommands}}\n <div class=\"flex items-center\">\n <div class=\"w-14\"><img src=\"/images/gear-set-blue.png\""
},
{
"path": "views/layouts/masterLayout.hbs",
"chars": 3146,
"preview": "<!DOCTYPE html>\n<html class=\"font-sans\">\n\n<head>\n <title>{{title}}</title>\n <link href=\"/styles/styles.css\" rel=\"style"
},
{
"path": "views/newcommand.hbs",
"chars": 1631,
"preview": "<form class=\"flex flex-col space-y-4 mb-6\" method=\"post\">\n <div class=\"flex flex-col\">\n <label class=\"mb-2 font-bold"
}
]
About this extraction
This page contains the full source code of the DanWahlin/NodeExpressMongoDBDockerApp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (31.8 KB), approximately 9.7k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.