From 0bf9fa7c2fd873ca4df33e536104ff8972cec65d Mon Sep 17 00:00:00 2001 From: Hamish Willee Date: Fri, 13 Feb 2026 16:07:14 +1100 Subject: [PATCH] Router uses DELETE verb for delete - all changes --- README.md | 20 +++++++++++++++++-- app.js | 2 ++ package-lock.json | 32 ++++++++++++++++++++++++++++++- package.json | 1 + routes/catalog.js | 28 +++++++++++++-------------- test/routes/author.test.mjs | 4 ++-- test/routes/book.test.mjs | 4 ++-- test/routes/bookinstance.test.mjs | 2 +- test/routes/genre.test.mjs | 4 ++-- views/author_delete.pug | 4 ++-- views/book_delete.pug | 2 +- views/bookinstance_delete.pug | 4 ++-- views/genre_delete.pug | 4 ++-- 13 files changed, 80 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index e2505725..9fbb3e67 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,31 @@ To get this project up and running locally on your computer: ```bash npm install ``` + 3. Run the tutorial server, using the appropriate command line shell for your environment: ```bash # Linux terminal DEBUG=express-locallibrary-tutorial:* npm run devstart - + # Windows Powershell $ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start ``` + 4. Open a browser to to open the library site. -> **Note:** The library uses a default MongoDB database hosted on [MongoDB Atlas](https://www.mongodb.com/cloud/atlas). You should use a different database for your own code experiments. +> [!NOTE] +> The library uses a default MongoDB database hosted on [MongoDB Atlas](https://www.mongodb.com/cloud/atlas). +> You should use a different database for your own code experiments. + +## Contributing + +The project is the result of carefully running through all the steps in the [tutorial on MDN](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website). +Any changes to either MDN or this project must be synchronized. +Generally it is better to implement changes here before submitting them to MDN. + +Before submitting a PR, make sure that the tests pass: + +```bash +npm test +``` diff --git a/app.js b/app.js index 7e4c3155..6f252995 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,7 @@ const path = require("path"); const cookieParser = require("cookie-parser"); const logger = require("morgan"); const RateLimit = require("express-rate-limit"); +const methodOverride = require("method-override"); const indexRouter = require("./routes/index"); const usersRouter = require("./routes/users"); @@ -30,6 +31,7 @@ app.use(logger("dev")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); +app.use(methodOverride("_method")); app.use( helmet.contentSecurityPolicy({ diff --git a/package-lock.json b/package-lock.json index ca2cb10b..1bd0c1fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "helmet": "^8.1.0", "http-errors": "^2.0.0", "luxon": "^3.6.1", + "method-override": "^3.0.0", "mongoose": "^8.16.2", "morgan": "^1.10.0", "pug": "^3.0.3" @@ -1393,11 +1394,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "license": "MIT", + "dependencies": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/method-override/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/method-override/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" diff --git a/package.json b/package.json index 158503c8..6cb711a3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "helmet": "^8.1.0", "http-errors": "^2.0.0", "luxon": "^3.6.1", + "method-override": "^3.0.0", "mongoose": "^8.16.2", "morgan": "^1.10.0", "pug": "^3.0.3" diff --git a/routes/catalog.js b/routes/catalog.js index 23f2a664..97f2dc2e 100644 --- a/routes/catalog.js +++ b/routes/catalog.js @@ -22,8 +22,8 @@ router.post("/book/create", book_controller.book_create_post); // GET request to delete Book. router.get("/book/:id/delete", book_controller.book_delete_get); -// POST request to delete Book. -router.post("/book/:id/delete", book_controller.book_delete_post); +// DELETE request to delete Book. +router.delete("/book/:id/delete", book_controller.book_delete_post); // GET request to update Book. router.get("/book/:id/update", book_controller.book_update_get); @@ -48,8 +48,8 @@ router.post("/author/create", author_controller.author_create_post); // GET request to delete Author. router.get("/author/:id/delete", author_controller.author_delete_get); -// POST request to delete Author -router.post("/author/:id/delete", author_controller.author_delete_post); +// DELETE request to delete Author +router.delete("/author/:id/delete", author_controller.author_delete_post); // GET request to update Author. router.get("/author/:id/update", author_controller.author_update_get); @@ -74,8 +74,8 @@ router.post("/genre/create", genre_controller.genre_create_post); // GET request to delete Genre. router.get("/genre/:id/delete", genre_controller.genre_delete_get); -// POST request to delete Genre. -router.post("/genre/:id/delete", genre_controller.genre_delete_post); +// DELETE request to delete Genre. +router.delete("/genre/:id/delete", genre_controller.genre_delete_post); // GET request to update Genre. router.get("/genre/:id/update", genre_controller.genre_update_get); @@ -94,37 +94,37 @@ router.get("/genres", genre_controller.genre_list); // GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id). router.get( "/bookinstance/create", - book_instance_controller.bookinstance_create_get + book_instance_controller.bookinstance_create_get, ); // POST request for creating BookInstance. router.post( "/bookinstance/create", - book_instance_controller.bookinstance_create_post + book_instance_controller.bookinstance_create_post, ); // GET request to delete BookInstance. router.get( "/bookinstance/:id/delete", - book_instance_controller.bookinstance_delete_get + book_instance_controller.bookinstance_delete_get, ); -// POST request to delete BookInstance. -router.post( +// DELETE request to delete BookInstance. +router.delete( "/bookinstance/:id/delete", - book_instance_controller.bookinstance_delete_post + book_instance_controller.bookinstance_delete_post, ); // GET request to update BookInstance. router.get( "/bookinstance/:id/update", - book_instance_controller.bookinstance_update_get + book_instance_controller.bookinstance_update_get, ); // POST request to update BookInstance. router.post( "/bookinstance/:id/update", - book_instance_controller.bookinstance_update_post + book_instance_controller.bookinstance_update_post, ); // GET request for one BookInstance. diff --git a/test/routes/author.test.mjs b/test/routes/author.test.mjs index cf02ebb5..d7c10c41 100644 --- a/test/routes/author.test.mjs +++ b/test/routes/author.test.mjs @@ -115,7 +115,7 @@ test("Author Routes", async (t) => { "should delete the author and redirect to author list", async () => { const res = await request(app) - .post(`/catalog/author/${author._id}/delete`) + .delete(`/catalog/author/${author._id}/delete`) .type("form") .send({ authorid: author._id.toString() }) // Form body .expect(302); @@ -147,7 +147,7 @@ test("Author Routes", async (t) => { }); const res = await request(app) - .post(`/catalog/author/${author._id}/delete`) + .delete(`/catalog/author/${author._id}/delete`) .type("form") .send({ id: author._id.toString() }) // Note: Was `id`, but controller likely expects `authorid` for consistency .expect(200); // Should NOT redirect diff --git a/test/routes/book.test.mjs b/test/routes/book.test.mjs index 4eda62e1..4ca451a4 100644 --- a/test/routes/book.test.mjs +++ b/test/routes/book.test.mjs @@ -134,7 +134,7 @@ test("Book Routes", async (t) => { "should delete the book and redirect to book list", async () => { const res = await request(app) - .post(`/catalog/book/${book._id}/delete`) + .delete(`/catalog/book/${book._id}/delete`) .type("form") // Simulates form POST .send({ id: book._id.toString() }) // This is where server gets the ID .expect(302); @@ -166,7 +166,7 @@ test("Book Routes", async (t) => { }); const res = await request(app) - .post(`/catalog/book/${book._id}/delete`) + .delete(`/catalog/book/${book._id}/delete`) .type("form") .send({ id: book._id.toString() }) .expect(200); diff --git a/test/routes/bookinstance.test.mjs b/test/routes/bookinstance.test.mjs index d320e8f3..18bbe512 100644 --- a/test/routes/bookinstance.test.mjs +++ b/test/routes/bookinstance.test.mjs @@ -157,7 +157,7 @@ test("BookInstance Routes", async (t) => { "should delete the BookInstance and redirect to BookInstance list", async () => { const res = await request(app) - .post(`/catalog/bookinstance/${bookInstance._id}/delete`) + .delete(`/catalog/bookinstance/${bookInstance._id}/delete`) .type("form") .send({ id: bookInstance._id.toString() }) .expect(302); diff --git a/test/routes/genre.test.mjs b/test/routes/genre.test.mjs index 92977783..39dfb720 100644 --- a/test/routes/genre.test.mjs +++ b/test/routes/genre.test.mjs @@ -98,7 +98,7 @@ test("Genre Routes", async (t) => { "should delete the genre and redirect to genre list", async () => { const res = await request(app) - .post(`/catalog/genre/${genre._id}/delete`) + .delete(`/catalog/genre/${genre._id}/delete`) .type("form") .send({ id: genre._id.toString() }) .expect(302); @@ -134,7 +134,7 @@ test("Genre Routes", async (t) => { }); const res = await request(app) - .post(`/catalog/genre/${genre._id}/delete`) + .delete(`/catalog/genre/${genre._id}/delete`) .type("form") .send({ id: genre._id.toString() }) .expect(200); // Should NOT redirect, but render page with error diff --git a/views/author_delete.pug b/views/author_delete.pug index fa14ee9c..8f0f6dbe 100644 --- a/views/author_delete.pug +++ b/views/author_delete.pug @@ -19,8 +19,8 @@ block content else p Do you really want to delete this Author? - form(method='POST') + form(method='POST' action='?_method=DELETE') div.form-group input#authorid.form-control(type='hidden', name='authorid', value=author._id ) - button.btn.btn-primary(type='submit') Delete \ No newline at end of file + button.btn.btn-primary(type='submit') Delete diff --git a/views/book_delete.pug b/views/book_delete.pug index 4c33f538..bbad7509 100644 --- a/views/book_delete.pug +++ b/views/book_delete.pug @@ -41,7 +41,7 @@ block content else p Do you really want to delete this Book? - form(method='POST') + form(method='POST' action='?_method=DELETE') div.form-group input#id.form-control(type='hidden',name='id', value=book._id ) diff --git a/views/bookinstance_delete.pug b/views/bookinstance_delete.pug index 84db6044..f376ecdc 100644 --- a/views/bookinstance_delete.pug +++ b/views/bookinstance_delete.pug @@ -26,8 +26,8 @@ block content if bookinstance.status!='Available' p #[strong Due back:] #{bookinstance.due_back_formatted} - form(method='POST') + form(method='POST' action='?_method=DELETE') div.form-group input#id.form-control(type='hidden',name='id', value=bookinstance._id ) - button.btn.btn-primary(type='submit') Delete \ No newline at end of file + button.btn.btn-primary(type='submit') Delete diff --git a/views/genre_delete.pug b/views/genre_delete.pug index f025ba99..9dd40439 100644 --- a/views/genre_delete.pug +++ b/views/genre_delete.pug @@ -21,8 +21,8 @@ block content else p Do you really want to delete this Genre? - form(method='POST') + form(method='POST' action='?_method=DELETE') div.form-group input#id.form-control(type='hidden', name='id', value=genre._id ) - button.btn.btn-primary(type='submit') Delete \ No newline at end of file + button.btn.btn-primary(type='submit') Delete