Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
npm-debug.log
.git
.gitignore
Dockerfile*
README.md
.vscode
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=pokedex
MYSQL_USER=pokedex_user
MYSQL_PASSWORD=pokedex_password
POKEDEX_POKEMON_LIMIT=300
3 changes: 3 additions & 0 deletions .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ coverage
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Local environment values
.env

# Bower dependency directory (https://bower.io/)
bower_components

Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}
133 changes: 133 additions & 0 deletions ABOUT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# About Pokedex Full Stack App

A full stack Pokedex application that fetches Pokemon data from the [PokeAPI](https://pokeapi.co/) and displays it in a browser. The app runs entirely with Docker Compose — one command starts the database, seeds it with data, serves the API, and hosts the frontend.

---

## Tech Stack

| Layer | Technology |
|---|---|
| Frontend | HTML, CSS, JavaScript (nginx static server) |
| API | Node.js, Express |
| Database | MySQL 8.4 |
| Container runtime | Docker Compose |
| External data source | PokeAPI |

---

## Infrastructure Design

```mermaid
flowchart TD
Browser(["🌐 Browser\nlocalhost:8080"])

subgraph Compose Network ["Docker Compose Network"]
Frontend["frontend\nnginx:1.27-alpine\nport 80 → 8080"]
API["api\nnode:20-alpine\nport 5000"]
Seed["seed\nnode:20-alpine\none-shot container"]
MySQL["mysql\nmysql:8.4\nport 3306"]
Volume[("mysql_data\nnamed volume")]
end

PokeAPI(["🌍 PokeAPI\npokeapi.co"])

Browser -->|"GET /\nGET /api/pokedex"| Frontend
Frontend -->|"proxy /api/ →\nhttp://api:5000/"| API
API -->|"SELECT pokemon, types,\npoke_type"| MySQL
Seed -->|"INSERT pokemon,\ntypes, poke_type"| MySQL
Seed -->|"fetch pokemon 1–300"| PokeAPI
MySQL --- Volume

style Seed stroke-dasharray: 5 5
```

> The `seed` service (dashed) is a one-shot container. It runs after MySQL is healthy, loads data from PokeAPI, then exits. On subsequent startups it skips if the `pokemon` table already has rows.

---

## Startup Sequence

```mermaid
sequenceDiagram
participant D as Docker Compose
participant M as mysql
participant S as seed
participant A as api
participant F as frontend

D->>M: start mysql container
M-->>D: healthcheck passes
D->>S: start seed (depends on mysql healthy)
S->>M: wait for DB connection
S->>PokeAPI: fetch 300 pokemon
S->>M: INSERT pokemon / types / poke_type
S-->>D: exit 0
D->>A: start api (depends on seed completed)
D->>F: start frontend (depends on api started)
```

---

## Data Model

```mermaid
erDiagram
pokemon {
INT id PK
VARCHAR name
VARCHAR img
}
types {
INT id PK
VARCHAR name
}
poke_type {
INT id PK
INT pokeId FK
INT typeId FK
}

pokemon ||--o{ poke_type : "has"
types ||--o{ poke_type : "tagged by"
```

---

## API

| Method | Path | Response |
|---|---|---|
| GET | `/pokedex` | `{ id, name, type, img }[]` |

---

## Running the App

```bash
# First run (builds images, seeds DB automatically)
docker compose up --build

# Subsequent runs
docker compose up

# Manual reseed
docker compose exec api npm run db:reseed

# Full reset (wipes DB volume)
docker compose down -v && docker compose up --build
```

---

## Environment Variables

Defaults are defined in `.env.example`. Copy to `.env` to override:

| Variable | Default | Description |
|---|---|---|
| `MYSQL_ROOT_PASSWORD` | `rootpassword` | MySQL root password |
| `MYSQL_DATABASE` | `pokedex` | Database name |
| `MYSQL_USER` | `pokedex_user` | App DB user |
| `MYSQL_PASSWORD` | `pokedex_password` | App DB password |
| `POKEDEX_POKEMON_LIMIT` | `300` | Number of Pokemon to seed |
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install --omit=dev

COPY . .

EXPOSE 5000
CMD ["npm", "start"]
7 changes: 7 additions & 0 deletions Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM nginx:1.27-alpine

COPY web/nginx.conf /etc/nginx/conf.d/default.conf
COPY web/index.html /usr/share/nginx/html/index.html
COPY web/index.js /usr/share/nginx/html/index.js
COPY web/pokemon.css /usr/share/nginx/html/pokemon.css
COPY web/config.docker.js /usr/share/nginx/html/config.js
77 changes: 73 additions & 4 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,79 @@

![pickachu](https://media.giphy.com/media/uLnPIWsqIz2aA/giphy.gif)

You'll be creating a pokedex, a place to view info on all the Pokemon
This project runs a full Pokedex stack with Docker Compose:

- `mysql` for persistent database storage
- `api` (Express + MySQL driver)
- `frontend` (nginx serving static web files)

On first startup, schema is created automatically and Pokemon data is seeded from PokeAPI.

## Quick Start (Docker Compose)

1. Optional: copy environment defaults.

```bash
cp .env.example .env
```

2. Start the full stack.

```bash
docker compose up --build
```

3. Open the app.

- Frontend: http://localhost:8080
- API: http://localhost:5000/pokedex
- MySQL: localhost:3306

## Database Initialization and Seed Behavior

- Schema initialization runs from `data/schema.sql` using MySQL's init hook.
- The `seed` service runs `npm run seed:if-empty`.
- If `pokemon` already has rows, seed is skipped.
- If the MySQL volume is fresh, all initial data is loaded.

## Manual Database Reseed

Run reseed inside the API container:

```bash
docker compose exec api npm run db:reseed
```

Or run only seed (without reset):

```bash
docker compose exec api npm run seed
```

## Full Reset (including MySQL data)

```bash
docker compose down -v
docker compose up --build
```

`-v` deletes the named MySQL volume, so the next startup performs first-run initialization again.

## Environment Variables

Use `.env` (optional) to override defaults:

- `MYSQL_ROOT_PASSWORD`
- `MYSQL_DATABASE`
- `MYSQL_USER`
- `MYSQL_PASSWORD`
- `POKEDEX_POKEMON_LIMIT`

## Local (Non-Docker) Notes

- Frontend API base URL is configured in `web/config.js`.
- Default is `http://localhost:5000`.
- For Docker frontend image, a container-specific config points to `/api` via nginx proxy.

## Requirements
* The user can view all the pokemon.
Expand Down Expand Up @@ -35,6 +107,3 @@ You'll be creating a pokedex, a place to view info on all the Pokemon

## External Services
[PokeAPI](https://pokeapi.co/)

## Submission
Push to GitHub and create a pull request
40 changes: 40 additions & 0 deletions data/createTables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const mysql = require('mysql');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend creating your schema with a sql script. You can run the script whenever you want to recreate your database


const con = mysql.createConnection({
host: "localhost",
user: "root",
password: "*****",
database: "pokedex"
});

con.connect(function(err) {
if (err) throw err;
console.log("Connected!");
let sql = "CREATE TABLE pokemon(id INT PRIMARY KEY NOT NULL,name VARCHAR(50),img VARCHAR(256))";
con.query(sql, function (err, result) {
if (err) throw err;
console.log("pokemon table created...");
});
}),

con.connect(function(err) {
if (err) throw err;
console.log("Connected!");
let sql = "CREATE TABLE types(id INT PRIMARY KEY NOT NULL,name VARCHAR(32))";
con.query(sql, function (err, result) {
if (err) throw err;
console.log("types table created...");
});
}),

con.connect(function(err) {
if (err) throw err;
console.log("Connected!");
let sql = "CREATE TABLE poke_type(id INT PRIMARY KEY AUTO_INCREMENT,pokeId INT NOT NULL,typeId INT NOT NULL,FOREIGN KEY (pokeId) REFERENCES pokemon(id),FOREIGN KEY (typeId) REFERENCES type(id))";
con.query(sql, function (err, result) {
if (err) throw err;
console.log("mapping table created...");
});

con.end ()
});
44 changes: 44 additions & 0 deletions data/resetDatabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const mysql = require("mysql2");

const DB_CONFIG = {
host: process.env.DB_HOST || "localhost",
port: Number(process.env.DB_PORT || 3306),
user: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "",
database: process.env.DB_NAME || "pokedex"
};

function createConnection() {
return mysql.createConnection(DB_CONFIG);
}

function query(connection, sql) {
return new Promise((resolve, reject) => {
connection.query(sql, (err, results) => {
if (err) {
reject(err);
return;
}
resolve(results);
});
});
}

async function reset() {
const connection = createConnection();
try {
await query(connection, "SET FOREIGN_KEY_CHECKS = 0");
await query(connection, "TRUNCATE TABLE poke_type");
await query(connection, "TRUNCATE TABLE types");
await query(connection, "TRUNCATE TABLE pokemon");
await query(connection, "SET FOREIGN_KEY_CHECKS = 1");
console.log("Database tables truncated.");
} finally {
connection.end();
}
}

reset().catch((error) => {
console.error("Reset failed:", error.message);
process.exit(1);
});
19 changes: 19 additions & 0 deletions data/schema.sql
100644 → 100755
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
CREATE TABLE IF NOT EXISTS pokemon(
id INT PRIMARY KEY NOT NULL,
name VARCHAR(50),
img VARCHAR(256)
);

CREATE TABLE IF NOT EXISTS types(
id INT PRIMARY KEY NOT NULL,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend using auto increment here as well

name VARCHAR(32)
);

CREATE TABLE IF NOT EXISTS poke_type(
id INT PRIMARY KEY AUTO_INCREMENT,
pokeId INT NOT NULL,
typeId INT NOT NULL,
UNIQUE KEY unique_poke_type (pokeId, typeId),
FOREIGN KEY (pokeId) REFERENCES pokemon(id),
FOREIGN KEY (typeId) REFERENCES types(id)
);
Loading