Skip to content

Joan Nabuuma #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
61 changes: 35 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
In this workshop we're going to look at how to use express with a postgres database.

## Learning Objectives
* Explain how REST API methods interact with a relational database
* Implement a REST API backed by a database using express and postgres

- Explain how REST API methods interact with a relational database
- Implement a REST API backed by a database using express and postgres

## Setup

Expand All @@ -20,16 +21,16 @@ For this exercise we will also need to configure our database:

6. Copy the SQL from the files in the `sql/` directory and run them in TablePlus

* create-books.sql
* create-pets.sql
* insert-books.sql
* insert-pets.sql
- create-books.sql
- create-pets.sql
- insert-books.sql
- insert-pets.sql

7. Copy the URL of your instance

8. Create a file `.env` in the __root directory__ of your project. It should be right next to the `.env.example` file. It should contain a single line, which contains the *environment variable* used to specify the url from the instance created above. See the example file for reference.
8. Create a file `.env` in the **root directory** of your project. It should be right next to the `.env.example` file. It should contain a single line, which contains the _environment variable_ used to specify the url from the instance created above. See the example file for reference.

7. Type `npm start`, which starts a development server that will reload whenever you make any changes to source files.
9. Type `npm start`, which starts a development server that will reload whenever you make any changes to source files.

All being well, you will have a terminal window that looks like the following:

Expand All @@ -38,60 +39,68 @@ All being well, you will have a terminal window that looks like the following:
_Figure 2: The terminal window where the express server is running successfully_

## Interacting with the Database

To interact with the database we will use the [node-postgres](https://node-postgres.com/) library. We will use the [query](https://node-postgres.com/features/queries) method to send SQL queries to the database sever and receive responses. The `db/index.js` file establishes the connection to the database. Your instructor will walk through this with you.

## Demo

Your instructor will demonstrate implementing some of the books API, now using a real database. You will complete the API spec implementation

## Instructions

- Implement the [API spec](https://boolean-uk.github.io/api-express-database/standard)

## Tests

Run the following commands from your project directory to run the test suites:

```sh
$ npm test # standard criteria
$ npm run test-extensions # extension criteria
npm test # standard criteria
npm run test-extensions # extension criteria
```

You can also focus on one test at a time - use the [jest docs](https://jestjs.io/docs/cli) to help filter which tests to run. We recommend you run tests manually with the option `--forceExit`.

For example, for the following test:

```js
it("will list all books", async () => {
const response = await supertest(app).get("/books")
const response = await supertest(app).get("/books");

expect(response.status).toEqual(200)
expect(response.body.books).not.toEqual(undefined)
expect(response.body.books.length).toEqual(2)
const expectedBooks = [book1, book2]
expect(response.status).toEqual(200);
expect(response.body.books).not.toEqual(undefined);
expect(response.body.books.length).toEqual(2);
const expectedBooks = [book1, book2];
response.body.books.forEach((retrievedBook, index) => {
expect(retrievedBook.title).toEqual(expectedBooks[index].title)
})
})
expect(retrievedBook.title).toEqual(expectedBooks[index].title);
});
});
```

Here are two ways to run it.

```sh
$ npx jest -t "will list all books" --forceExit
$ npx jest test/api/routes/books.spec.js --forceExit # remember to add the 'f' before it()
npx jest -t "will list all books" --forceExit
npx jest test/api/routes/books.spec.js --forceExit # remember to add the 'f' before it()
```

## Extension 1

- Implement the [extension API spec](https://boolean-uk.github.io/api-express-database/extension)
- This API spec has some of the same endpoints as the Standard Criteria API spec, but they are **in addition to / build
on top of** that one.
- This API spec has some of the same endpoints as the Standard Criteria API spec, but they are **in addition to / build
on top of** that one.

## Extension 2

So far we've been including all our database code directly in our route handlers. In a real application, this is considered bad practice. It would become difficult to maintain as the code base grows, and we are also mixing concerns. We have routing code, request/response handling and database access all in a single function. This leads to tight coupling and low cohesion.

It is better practice to split your code into different *layers*. This helps keep our code decoupled. Rather than your route handler implementing all your logic, you can introduce controller functions that handle your routes as well as specific functions for calling the database. There is no single "correct" approach, but here are some examples:
It is better practice to split your code into different _layers_. This helps keep our code decoupled. Rather than your route handler implementing all your logic, you can introduce controller functions that handle your routes as well as specific functions for calling the database. There is no single "correct" approach, but here are some examples:

* [MDN Express controllers and routes](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes)
* [Express REST API structure](https://www.coreycleary.me/project-structure-for-an-express-rest-api-when-there-is-no-standard-way)
- [MDN Express controllers and routes](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes)
- [Express REST API structure](https://www.coreycleary.me/project-structure-for-an-express-rest-api-when-there-is-no-standard-way)

There is also an example boolean repository that provides a suggested structure:

* [API Express Architecture](https://github.com/boolean-uk/api-express-architecture-example)
- [API Express Architecture](https://github.com/boolean-uk/api-express-architecture-example)

Update your implementation to match the structure of the above repo. Controllers functions should handle your requests and responses, and repository functions should handle your database access.
2 changes: 1 addition & 1 deletion db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const client = {
// on the contents of our env file
// Create a new connection to the database using the Client
// object provided by the postgres node module
const dbClient = new Client(process.env.PGURL)
const dbClient = new Client(process.env.DATABASE_URL)
// connect a connection
await dbClient.connect()
// execute the query
Expand Down
67 changes: 15 additions & 52 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions src/data/books.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const db = require("./db.js");


const all = async () => {
const result = await db.query("SELECT * FROM books");
return result.rows;
};

const getById = async (id) => {
const result = await db.query("SELECT * FROM books WHERE id = $1", [id]);
return result.rows[0];
};

const create = async (book) => {
const result = await db.query(
"INSERT INTO books (title, type, author, topic, publication_date, pages) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *",
[
book.title,
book.type,
book.author,
book.topic,
book.publication_date,
book.pages,
]
);
return result.rows[0];
};

const update = async (id, updates) => {
const result = await db.query(
"UPDATE books SET title = $1, type = $2, author = $3, topic = $4, publication_date = $5, pages = $6 where id = $7 RETURNING *",
[
updates.name,
updates.type,
updates.author,
updates.topic,
updates.publication_date,
updates.pages,
id,
]
);
return result.rows[0];
};

const remove = async (id) => {
const result = await db.query(
"DELETE FROM books WHERE id = $1 RETURNING *",
[id]
);
return result.rows[0];
};

module.exports = {
all,
getById,
create,
update,
remove,
};
23 changes: 23 additions & 0 deletions src/data/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { Client } = require("pg");
require('dotenv').config();

const { HOST, PORT, DATABASE, USERNAME, PASSWORD, DATABASE_URL } = process.env;

//const db = new Pool({
//host: PGHOST,
//database: PGDATABASE,
//username: PGUSER,
//password: PGPASSWORD,
//});

const db = new Client(DATABASE_URL);

const init = async () => {
await db.connect();
};
init()




module.exports = db;
49 changes: 49 additions & 0 deletions src/data/pets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const db = require("./db.js");

const all = async () => {
const result = await db.query("SELECT * FROM pets");
return result.rows;
};

const getById = async (id) => {
const result = await db.query("SELECT * FROM pets WHERE id = $1", [id]);
return result.rows[0];
};

const create = async (pet) => {
const result = await db.query(
"INSERT INTO pets (name, age, type, breed, has_microchip) VALUES ($1, $2, $3, $4, $5) RETURNING *",
[pet.name, pet.age, pet.type, pet.breed, pet.has_microchip]
);
return result.rows[0];
};

const update = async (id, updates) => {
const result = await db.query(
"UPDATE pets SET name = $1, age = $2, type = $3, breed = $4, has_microchip = $5 where id = $6 RETURNING *",
[
updates.name,
updates.age,
updates.type,
updates.breed,
updates.has_microchip,
id,
]
);
return result.rows[0];
};

const remove = async (id) => {
const result = await db.query("DELETE FROM pets WHERE id = $1 RETURNING *", [
id,
]);
return result.rows[0];
};

module.exports = {
all,
getById,
create,
update,
remove,
};
Loading