Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
abuniatov authored Sep 16, 2024
0 parents commit 9ee714a
Show file tree
Hide file tree
Showing 54 changed files with 7,668 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_STORE
227 changes: 227 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
<img src="./images/hyf.svg" alt="image" width="200px" height="200px">

# HackYourFuture project template

This template is meant to be used as part of the meal-sharing and Final Project modules,
but can work as a starting point for any full stack project.

It consists of two packages:

- `api` which is a NodeJS project using Express for the API
- `app` which is Vite project using React for the web app

Both packages are as small as possible but feel free to add more tools as you see fit.

## Prerequisites

This template assumes that there is a database already set up with tables and data.

You can start a MySQL instance using Docker with the below command:
`docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql-root-password -e MYSQL_DATABASE=my-database -d -p 3306:3306 mysql:latest`

Then connect to this instance using any database management tool you prefer, such as MySQL Workbench, to set up your tables and add data.
![Testing your database](./images/db_test.png)

## Getting started

> Before you start, make sure no other projects are running, in order to have the ports free.
To get started you'll need two terminals.

In the first terminal run the following commands:

```
cd api
cp .env-example .env
npm install
npm run dev
```

You can then test the API using [Postman](https://www.postman.com/) at [http://localhost:3001/api](http://localhost:3001/api).
![Testing the API with Postman](./images/api_test.png)

In the second terminal run the following commands:

```
cd app
npm install
npm run dev
```

You can then open the web app at [http://localhost:3000](http://localhost:3000).
![Testing the app with a browser](./images/app_test.png)

## Common issues

### Port conflict

If you see the below error when trying to start either the API or the web app, then you have a port conflict.
Some other process is already listening on the port you want to use. Either stop that process or use another port by changing the PORT variable in the `.env` file for the package that is facing the conflict (API or web app).

```
node:events:496
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use :::3001
at Server.setupListenHandle [as _listen2] (node:net:1897:16)
at listenInCluster (node:net:1945:12)
at Server.listen (node:net:2037:7)
at Function.listen (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/express/lib/application.js:635:24)
at file:///Users/hyf/dev/hyf/hyf-project-template/api/src/index.js:29:5
at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:323:24)
at async loadESM (node:internal/process/esm_loader:28:7)
at async handleMainPromise (node:internal/modules/run_main:113:12)
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1924:8)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'EADDRINUSE',
errno: -48,
syscall: 'listen',
address: '::',
port: 3001
}
```

### Wrong database credentials

If you see the error below when trying to make a query to your database it means that the variables `DB_USER` and `DB_PASSWORD` don't match the username and password used when starting the database container. Either find the right credentials or recreate your database container and store the new credentials.

```
/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/packets/packet.js:728
const err = new Error(message);
^
Error: Access denied for user 'root'@'192.168.65.1' (using password: YES)
at Packet.asError (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/packets/packet.js:728:17)
at ClientHandshake.execute (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/commands/command.js:29:26)
at Connection.handlePacket (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/connection.js:481:34)
at PacketParser.onPacket (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/connection.js:97:12)
at PacketParser.executeStart (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/packet_parser.js:75:16)
at Socket.<anonymous> (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/mysql2/lib/connection.js:104:25)
at Socket.emit (node:events:519:28)
at addChunk (node:internal/streams/readable:559:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
at Readable.push (node:internal/streams/readable:390:5) {
code: 'ER_ACCESS_DENIED_ERROR',
errno: 1045,
sqlState: '28000',
sqlMessage: "Access denied for user 'root'@'192.168.65.1' (using password: YES)",
sql: undefined
}
```

### Using SSL when the database does not support it

If you see the below error, then you are trying to establish a SSL (secure) connection to a database that doesn't support it, most likely you set the value of the environment variable `DB_USE_SSL` to "true" when it should be "false".

```
/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/pg/lib/connection.js:77
return self.emit('error', new Error('The server does not support SSL connections'))
^
Error: The server does not support SSL connections
at Socket.<anonymous> (/Users/hyf/dev/hyf/hyf-project-template/api/node_modules/pg/lib/connection.js:77:37)
at Object.onceWrapper (node:events:633:26)
at Socket.emit (node:events:518:28)
at addChunk (node:internal/streams/readable:559:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
at Readable.push (node:internal/streams/readable:390:5)
at TCP.onStreamRead (node:internal/stream_base_commons:190:23)
Node.js v20.12.2
[nodemon] app crashed - waiting for file changes before starting...
Database not running or wrong address:
API listening on port 3001
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1116:18)
at afterConnectMultiple (node:net:1683:7) {
code: 'ECONNREFUSED',
fatal: true,
[errors]: [
Error: connect ECONNREFUSED ::1:3306
at createConnectionError (node:net:1646:14)
at afterConnectMultiple (node:net:1676:16) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '::1',
port: 3306
},
Error: connect ECONNREFUSED 127.0.0.1:3306
at createConnectionError (node:net:1646:14)
at afterConnectMultiple (node:net:1676:16) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 3306
}
]
}
```

### Broken TypeScript config file
This boilerplate does not use TypeScript, but Vite which is used in the `app` package has built in support for TypeScript.
This means that Vite (or rather `esbuild` which is used internally) will look for a TypScript config file (`tsconfig.json`) in the current folder and all parent folders until it finds one.
If it does find a TypeScript config file but that file is invalid or empty you'll see errors like this:

```
✘ [ERROR] Unexpected end of file in JSON
../../tsconfig.json:1:0:
1 │
╵ ^
failed to load config from /Users/milton/dev/hyf/hyf-project-template/app/vite.config.js
error when starting dev server:
Error: Build failed with 1 error:
../../tsconfig.json:1:0: ERROR: Unexpected end of file in JSON
at failureErrorWithLog (/Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:1651:15)
at /Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:1059:25
at runOnEndCallbacks (/Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:1486:45)
at buildResponseToResult (/Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:1057:7)
at /Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:1086:16
at responseCallbacks.<computed> (/Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:704:9)
at handleIncomingPacket (/Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:764:9)
at Socket.readFromStdout (/Users/milton/dev/hyf/hyf-project-template/app/node_modules/esbuild/lib/main.js:680:7)
at Socket.emit (node:events:519:28)
at addChunk (node:internal/streams/readable:559:12)
```

There are 3 possible solutions:
1. Delete the offending TypeScript config file
2. Fix the issue in the file so that Vite can use it
3. Move your project folder to some place that doesn't put the TypeScript config file in the parent folder of your project folder

## Architecture diagram

![Architecture](./images/architecture.png)

## Applying consistent formatting

Before you commit any changes you've made, you can run the command `npm run format` in either package to format the code using [Prettier](https://prettier.io/).

Using a consistent code style makes it easier to read code which improves productivity and avoid bugs.
When collaborating with other people, a code base should still look like it was written by a single person.

## Checking for common code problems

Before you commit your changes, you can use `npm run check` in either package to check for code issues using [ESLint](https://eslint.org/).

ESLint is a "linter", a tool that scans your code for common code problems, this can help you avoid bugs and write better code.

## Deploying

All 3 components (database, API, web app) can be deployed for free at [Render.com](https://render.com).
Sign in using your Github account to make the process smoother.
When you sign in you can specify which of your repositories you want Render.com to have access to.

[Database and API deployment instructions](./api/README.md#deploying)
[App deployment instructions](./app/README.md#deploying-a-static-web-app)
15 changes: 15 additions & 0 deletions api/.env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This is the port the API will be available at
PORT=3001

# Assuming that you started your MySQL container with the below command:
# docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql-root-password -e MYSQL_DATABASE=my-database -d -p 3306:3306 mysql:latest
DB_CLIENT=mysql2
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=mysql-root-password
DB_DATABASE_NAME=meal-sharing

# The database you use for local development most likely doesn't use SSL (encryption)
# but the one you deploy on Render.com does.
DB_USE_SSL=false
2 changes: 2 additions & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
128 changes: 128 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# The API package

This package sets up a [Express](https://expressjs.com/) API server and a connection to a SQL database using [Knex](https://knexjs.org/).

For development you can run the command `npm run dev` which uses `nodemon` to watch files and restarts the server when a change happens.
You can visit [http://localhost:3001/api](http://localhost:3001/api) which will test the connection to the database.

There is no build step so when deploying it is enough to run `npm run start`.

## Environment variables

You can set environment variables in the `.env` file or in the Render.com environment variables section.
When you start a fresh project, make sure to copy the `.env` file by using `cp .env-example .env`.

## Database clients

The package comes installed with both a MySQL client for local development and a PostgreSQL client to use on [Render.com](https://render.com).
You can change which client to use by changing the `DB_CLIENT` environment variable (either `mysql2` or `pg`).

## Database managed with code

You can get far with a simple `.sql` file to manage your database but if you'd prefer to manage your database with Knex, you can use [Knex Migrations](https://knexjs.org/guide/migrations.html) to set up your schema (as well as rollback schema changes across versions).
You can also use [Knex Seeds](https://knexjs.org/guide/migrations.html#seed-files) to populate your database with data.
Combined, these two techniques make it very easy to experiment with changes to your database or recover your database if something happens to it.
It also makes it possible to share temporary schema changes with others during Pull Request testing.

## Deploying

> Last tested: 2024-04-11
### Deploying a PostgreSQL database

From your Render.com Dashboard page, click the tile called PostGreSQL.

![](../images/render/database/step1.png)

In the next screen, fill in the marked fields, then scroll down.

![](../images/render/database/step2.png)

Select the "Free" tier. Then click "Create".

> Your database will be automatically deleted after 90 days, if you need it for longer simply recreate it following the same steps.
![](../images/render/database/step3.png)

On the next page, scroll down to the section "Connections".

![](../images/render/database/step4.png)

We need to copy the the following fields:

- Port
- Database
- Username
- Password
- External Database URL

We can put these into our `.env` file to test our database locally.

It's important to note that we need to extract only the host name from "External Database URL".
If the value you copied was:

> postgres://my_user:EiwuEVDpdGzoDRXTquSSXNMHoVmCh1qG@dpg-cobfi7i1hbls73e0dkt0-a.frankfurt-postgres.render.com/my_database_u9be
Then what you want to extract is:

> dpg-cobfi7i1hbls73e0dkt0-a.frankfurt-postgres.render.com
Your `.env` file should look something like this in the end:

```
PORT=3001
DB_CLIENT=pg
DB_HOST=dpg-cobfi7i1hbls73e0dkt0-a.frankfurt-postgres.render.com
DB_PORT=5432
DB_USER=my_user
DB_PASSWORD=EiwuEVDpdGzoDRXTquSSXNMHoVmCh1qG
DB_DATABASE_NAME=my_database_u9be
```

You can run `npm run dev` and visit `http://localhost:3001/api` to verify that your local API server is able to connect to your database on Render.com.

> You can use the same variables to connect to the database using a PostgreSQL management tool (such as [pgAdmin](https://www.pgadmin.org/)) to test and setup your database.
### Deploying an API server

If you go back to your Dashboard you should now see your database in your list of deployed services. From here click "New" and then select "Web Service".

![](../images/render/api/step5.png)
![](../images/render/api/step6.png)

We want to deploy from a Git repository, this is called GitOps. Each time we push a new commit to the Git repository, Render will update your deployed service.

![](../images/render/api/step7.png)

If you cannot find your Git repository, you may need to re-configure your Github account to allow Render to see the repository you want to deploy from.

![](../images/render/api/step8.png)

Click "Connect" for the repository you want to use (the one that is based on this template).

![](../images/render/api/step9.png)

In the next page, fill in all the required fields.

![](../images/render/api/step10.png)
![](../images/render/api/step11.png)

When you reach the section about "Environment variables", click the button called "Add from .env" which opens a dialog. You can copy the content of your `.env` file into this dialog (except for the PORT variable), then click "Add variables".

![](../images/render/api/step12.png)
![](../images/render/api/step13.png)

The page should look something like this after.
It's important here to change the value of the variable DB_USE_SSL from "false" to "true".
Finish up by clicking "Create Web Service".

![](../images/render/api/step14.png)

In the next screen you'll see the output of your build step which is downloading your code and deploying it.

![](../images/render/api/step15.png)

Once you see the text "Your service is live" you can test your API with Postman by using the deployed URL, which should be something like `https://hyf-template-api.onrender.com/api`. You should see the output from the database.

Next, let's deploy the web app by following the steps [here](../app/README.md#deploying).
7 changes: 7 additions & 0 deletions api/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import globals from "globals";
import pluginJs from "@eslint/js";

export default [
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
];
Loading

0 comments on commit 9ee714a

Please sign in to comment.