CRUD API — my notes ✏
from my own [Link] (chat app) — build order, top to bottom
[Link] setup
My actual [Link] imports + config →
const express = require("express"); // load the express package
const app = express(); // create the app/server object
const port = 3000; // port number to listen on
const mongoose = require("mongoose"); // load mongoose (talks to MongoDB)
const chat = require("./models/[Link]"); // my chat model
const path = require("path"); // node's built-in path helper
const methodOverride = require("method-override"); // fake PUT/DELETE from forms
[Link]("views", [Link](__dirname, "views")); // folder where .ejs files live
[Link]("view engine", "ejs"); // use EJS to render templates
[Link]([Link]({ extended: true })); // parse form data into [Link]
[Link](methodOverride("_method")); // read ?_method=PUT/DELETE from forms
chat = my Mongoose model, imported from models/[Link]
[Link](__dirname,...) = absolute path to views folder, safe on any OS
methodOverride("_method") = lets [Link] form send PUT, delete send DELETE
remember!
no [Link]() → [Link] is undefined → my form data never arrives
[Link] MongoDB
async function main() { // wrap so we can use await
await [Link]("mongodb://[Link]:27017/whatsapp");
// ^ waits for MongoDB to connect (creates "whatsapp" db if new)
}
main() // run the connect function
.then(() => { // runs if connection succeeds
[Link]("Database Connected"); // confirms DB is live
})
.catch((err) => { // runs if connection fails
[Link](err); // check this first if nothing saves!
});
→ connects to my local whatsapp database. connect() returns a Promise, so
await/then it. DB + collection get created automatically on first save!
[Link] & Model
[Link] & Model
Schema = rules for the data. Model = the tool you use to query/save.
(models/[Link])
const mongoose = require("mongoose"); // load mongoose
const chatSchema = new [Link]({ // define the shape of a chat
from:{
type: String, // must be text
required:true // save() throws if missing
},
to:{
type:String, // must be text
required:true // save() throws if missing
},
message:{
type:String,
maxLength:50 // save() throws if longer than this!
},
created_at:{
type:Date, // stores a timestamp
required:true // must be passed in manually (see step 4)
}
})
const chat = [Link]("chat",chatSchema); // build the model → "chats" collection
[Link] = chat; // so [Link] can require() it
note: "chat" model → auto becomes collection chats in db (lowercase + plural)
watch out!
message maxLength:50 → any message over 50 chars gets rejected by mongoose!
[Link] POST
my original route (from [Link]) — form → /chats:
[Link]("/chats", (req, res) => { // handles the form submit
let { from, to, message } = [Link]; // pull fields sent by the form
let newchat = new chat({ // build a new doc (not saved yet)
from,
to,
message,
created_at: new Date() // timestamp right now
});
[Link]() // write it to MongoDB (async!)
.then(() => { // runs if save succeeds
[Link]("Chat was saved");
})
.catch((err) => { // runs if save fails (bad data etc)
[Link](err); // error only goes to terminal ⚠
});
[Link]("/chats"); // fires BEFORE save() finishes!
});
the bug!
the bug!
redirect fires before save() finishes → if message > 50 chars, save fails silently, but page
still redirects like it worked
fixed version — await + try/catch so it actually waits:
[Link]("/chats", async (req, res) => { // now async, so we can await
try { // catches any error below
let { from, to, message } = [Link]; // pull fields sent by the form
let newchat = new chat({ from, to, message, created_at: new Date() }); // build doc
await [Link](); // wait for save to finish
[Link]("/chats"); // only runs if save succeeded
} catch (err) { // runs if save/validation failed
[Link](err); // log the real reason
[Link](400).send("Could not create chat: " + [Link]);
// ^ tell the client it failed, instead of redirecting anyway
}
});
[Link] GET
all chats (list page):
[Link]("/chats", async (req, res) => { // handles GET /chats
let chats = await [Link](); // ALL docs, as an array
[Link]("[Link]", { chats }); // pass to template
});
edit form for one chat:
[Link]("/chats/:id/edit", async (req, res) => { // :id is a URL placeholder
let { id } = [Link]; // grab :id from the URL
let c = await [Link](id); // one doc, or null
[Link]("[Link]", { c }); // send it to pre-fill the form
});
[Link]() = all docs (array) | [Link]() = one doc | [Link] pulls :id from
the URL
[Link] PUT
my route (from [Link]) — only updates the message:
[Link]("/chats/:id", async (req, res) => { // handles PUT /chats/:id
let { id } = [Link]; // which chat to update
let { message: newmsg } = [Link]; // only message is editable
await [Link](id, { // find by id + apply changes
message: newmsg
}); // NOTE: skips schema validation by default!
[Link]("/chats"); // back to the list
});
form sends this using ?_method=PUT since HTML forms can't do PUT directly
worth adding!
pass { runValidators: true } as a 3rd arg → otherwise a message over 50 chars can
sneak through on edit even though create blocks it
[Link] DELETE
my route (from [Link]):
[Link]("/chats/:id", async (req, res) => { // handles DELETE /chats/:id
let { id } = [Link]; // which chat to remove
await [Link](id); // find + remove in one call
[Link]("/chats"); // back to the list
});
same idea — delete button's form uses ?_method=DELETE
[Link] map
all 5 routes from my [Link], at a glance →
Route API (method) Task
/chats GET show all chats (list page)
/chats/new GET show the blank "new chat" form
/chats POST create — save a new chat
/chats/:id/edit GET show edit form, pre-filled with one chat
update — save the edited message
/chats/:id PUT
/chats/:id DELETE delete — remove one chat
quick debug checklist ✅
☑ DB actually connected? (check terminal)
☑ [Link] populated? (json/urlencoded middleware set?)
☑ awaited the save/update/delete?
☑ schema validation blocking it? (from/to required, message maxLength 50)
☑ update: runValidators:true set?
☑ HTML form PUT/DELETE: method-override + _method set?
build → connect → schema → create → read → update → delete