-
Notifications
You must be signed in to change notification settings - Fork 8
[Backend] NodeJS - week 2 #199
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
Changes from all commits
abedf07
7a7d46c
e2a2a25
e33a4f0
0447024
7cd0516
957204b
ee2163a
826fc83
8dfeb51
ed49536
3133f65
ae233a6
e160bbb
db71b66
f34e3ae
7c0cefe
aeaa73a
db336e0
776f142
bf506e2
95a0c57
9acfe5b
00b22d5
5b805a1
42a6daf
2ba2dfa
0738758
b9e63e6
6bed336
f32ad49
dad8970
49fae84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # Node (Week 2) | ||
|
|
||
| In this session we will focus on connecting to a database, building an API, and using Postman to test our API endpoints. We will also cover how to structure our code for better maintainability and scalability. | ||
|
|
||
| ## Contents | ||
|
|
||
| - [Preparation](./preparation.md) | ||
| - [Session Plan](./session-plan.md) (for mentors) | ||
| - [Assignment](./assignment.md) | ||
|
|
||
| ## Session Learning goals | ||
|
|
||
| By the end of this session, you will be able to: | ||
|
|
||
| - [ ] Manage advanced database interactions in your service | ||
| - [ ] Understand what Knex is and why to use it | ||
| - [ ] Set up connections to your database using Knex | ||
| - [ ] Execute `select`, `create`, `delete` and `update` queries using Knex Query Builder | ||
| - [ ] Implement all REST endpoints using Express | ||
| - [ ] `POST`, `DELETE`, `PUT` | ||
| - [ ] Use appropriate error handling to understand and debug issues | ||
| - [ ] Configure Postman for advanced backend development | ||
| - [ ] Creating collections and saving requests | ||
| - [ ] Set up multiple environments | ||
| - [ ] Managing secrets | ||
| - [ ] Create basic test suites |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # Assignment | ||
|
|
||
| You'll set up and work with your own version of a simple Contacts API. | ||
|
|
||
| It will start with one endpoint (and you will add more throughout the task): | ||
|
|
||
| - `GET /api/contacts` | ||
|
|
||
| This endpoint accepts a query parameter `sort`. Here's how it should be possible to use it: | ||
|
|
||
| - `GET /api/contacts?sort=first_name%20ASC` | ||
| - Sorts contacts by first name, ascending | ||
| - `GET /api/contacts?sort=last_name%20DESC` | ||
| - Sorts contacts by last name, descending | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Go to/create a `node/week2` directory in your `hyf-assignment` repo. | ||
| 2. Create yourself a new node application | ||
| 3. Create a database called `phonebook` with a `contacts` table, with the following schema and data: | ||
|
|
||
| ```sql | ||
| CREATE TABLE `contacts` ( | ||
| `id` int unsigned NOT NULL AUTO_INCREMENT, | ||
| `first_name` varchar(255) NOT NULL, | ||
| `last_name` varchar(255) NOT NULL, | ||
| `email` varchar(255) DEFAULT NULL, | ||
| `phone` varchar(255) DEFAULT NULL, | ||
| PRIMARY KEY (`id`) | ||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; | ||
|
|
||
| -- Sample data | ||
| insert into contacts (id, first_name, last_name, email, phone) values (1, 'Selig', 'Matussov', '[email protected]', '176-630-4577'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (2, 'Kenny', 'Yerrington', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (3, 'Emilie', 'Gaitskell', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (4, 'Jordon', 'Tokell', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (5, 'Sallyann', 'Persse', '[email protected]', '219-157-2368'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (6, 'Berri', 'Bulter', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (7, 'Lanni', 'Ivanilov', '[email protected]', null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (8, 'Dagny', 'Milnthorpe', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (9, 'Annadiane', 'Bansal', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (10, 'Tawsha', 'Hackley', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (11, 'Rubetta', 'Ozelton', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (12, 'Charles', 'Boughey', '[email protected]', '605-358-5664'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (13, 'Shantee', 'Robbe', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (14, 'Gleda', 'Peat', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (15, 'Arlinda', 'Ethersey', '[email protected]', '916-139-1300'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (16, 'Armando', 'Meachem', '[email protected]', '631-442-5339'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (17, 'Codi', 'Redhouse', null, '401-953-6897'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (18, 'Ann', 'Buncombe', '[email protected]', '210-338-0748'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (19, 'Louis', 'Matzkaitis', '[email protected]', '583-996-6979'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (20, 'Jessey', 'Pala', null, null); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (21, 'Archy', 'Scipsey', '[email protected]', '420-983-2426'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (22, 'Benoit', 'Mould', '[email protected]', '271-217-9218'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (23, 'Sherm', 'Girardey', '[email protected]', '916-999-2957'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (24, 'Raquel', 'Mudge', '[email protected]', '789-830-7473'); | ||
| insert into contacts (id, first_name, last_name, email, phone) values (25, 'Tabor', 'Reavey', null, null); | ||
| ``` | ||
|
|
||
| 4. Set up Express and an Sqlite connection in your node application. In your knex instance, make sure to set: `multipleStatements: true` - this is important! | ||
|
|
||
| 5. Make sure you have an API router under the `/api` path set up like so: | ||
|
|
||
| ```js | ||
| app.use("/api", apiRouter); | ||
| ``` | ||
|
|
||
| 6. Create a contacts router at `/contacts`, and attach it to your API router. | ||
| 7. In your contacts API, create the following endpoint: | ||
|
|
||
| ```js | ||
| contactsAPIRouter.get("/", async (req, res) => { | ||
| let query = knexInstance.select("*").from("contacts"); | ||
|
|
||
| if ("sort" in req.query) { | ||
| const orderBy = req.query.sort.toString(); | ||
| if (orderBy.length > 0) { | ||
| query = query.orderByRaw(orderBy); | ||
| } | ||
| } | ||
|
|
||
| console.log("SQL", query.toSQL().sql); | ||
|
|
||
| try { | ||
| const data = await query; | ||
| res.json({ data }); | ||
| } catch (e) { | ||
| console.error(e); | ||
| res.status(500).json({ error: "Internal server error" }); | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| ## The tasks | ||
|
|
||
| ### Task 1 - Solve the SQL injection | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've expanded these tasks to mirror a bit more of the things learned in the session. I'm not sure if it's too much to complete in a week (would appreciate some feedback on that), but at least conceptually it captures a nice little API and allows the trainees to practice the themes of today. To be honest, the SQL injection part is the one that sticks out the most, since it's not covered in the session. But, i don't think it's a bad idea to cover it again since it was taught in foundation, and is a nice practice to apply a more theoretical learning to now a much more practical implementation. And we also touch on some "insecure" knex practices in the error handling live coding example, which this builds on.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for assignment it is fine it's a lot of stuff. If it would be in class excecises, that can be tricky, but for home assignment it is good. There is nice practice now of check-in during the week so if anything is unclear hopefully the trainees will flag that and it can be explained then :) |
||
|
|
||
| The current implementation of the `sort` query parameter has introduced an SQL injection vulnerability. | ||
|
|
||
| First, you should demonstrate the SQL injection and that, for instance, it is possible to drop/delete the `contacts` table with the `sort` query parameter. Capture this demonstration with a screen recording, and attach it to your PR when you submit your assignment. | ||
|
|
||
| After having demonstrated the SQL injection vulnerability, your task is then to fix the issue. Your solution should be solved in the `app.js` file only. While the the `multipleStatements: true` configuration you used enables this vulnerability, it should not be changed in your solution. | ||
|
|
||
| ### Task 2 - Improve your API | ||
|
|
||
| Create two additional endpoints to enable the following functionality: | ||
|
|
||
| 1. Create new contacts | ||
| 2. Delete an existing contact | ||
|
|
||
| ### Task 3 - Error handling | ||
|
|
||
| Update your endpoints with appropriate error handling. You should, at least, handle the following cases: | ||
|
|
||
| 1. Successful requests | ||
| 2. Incorrect requests (e.g. an incorrectly formatted sort request) | ||
| 3. Server issues (e.g. a missing database table, or an offline database) | ||
| 4. A catch all for any other errors | ||
|
|
||
| Remember to: | ||
|
|
||
| 1. Return the appropriate HTTP code | ||
| 2. Avoid sending any implementation or internal data to the client | ||
| 3. Log an appropriate message so you can debug issues that occur in your service | ||
|
|
||
| ### Task 3 - Postman | ||
|
|
||
| 1. Create a Postman collection to capture some example requests with your new API. | ||
| 2. Create a basic test suite that you can run to validate that everything is working correctly. | ||
|
|
||
| Share both a link to your Collection and a link to a test run showing your tests passing in your pull request. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Preparation | ||
|
|
||
| - [NodeJS Web API with KNEX and Express](https://www.youtube.com/watch?v=QNw9q4YXR4E) (15 min) | ||
| - <https://fullstackopen.com/en/part3/node_js_and_express#rest> up until the `The Visual Studio Code REST client` section (15 min) | ||
| - Familiarise yourself with the [Knex Cheatsheet](https://devhints.io/knex) (especially Select, Scheme and Modifying sections) | ||
| - <https://jsonplaceholder.typicode.com/> - Free API for testing and prototyping. (5 min) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # PUT endpoint | ||
|
|
||
| TODO |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # DELETE endpoint | ||
|
|
||
| TODO |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import express from "express"; | ||
| import knex from "../database.js"; | ||
|
|
||
| const router = express.Router(); | ||
|
|
||
| // Example POST endpoint to add a new contact | ||
| router.post("/", async (request, response) => { | ||
| try { | ||
| console.log(response.body); // Server side log, for developers | ||
| const insertedContact = await knex("contacts").insert(response.body); // This could be insecure! | ||
| response.status(201).json(insertedContact); // 201 Created | ||
| } catch (error) { | ||
| console.error("Error inserting contact:", error); // Server side error, for developers | ||
| response | ||
| .status(500) | ||
| .json({ message: "Something went wrong on the server." }); // Client side error, for users. Avoid leaking database info. | ||
| } | ||
| }); | ||
|
|
||
| export default router; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import knex from "knex"; | ||
|
|
||
| // Run "sqlite3 phonebook.sqlite3 < phonebook.sql" to create this database first | ||
| const dbFile = "./phonebook.sqlite3"; | ||
|
|
||
| const knexInstance = knex({ | ||
| client: "sqlite3", | ||
| connection: { | ||
| filename: dbFile, | ||
| }, | ||
| }); | ||
|
|
||
| export default knexInstance; |
Uh oh!
There was an error while loading. Please reload this page.