CRUD, MVC, REST, INDUCES and JSX: Remember where we are going

Express Checklist
NOTE: Use your own variable names
Setup For Server Side Rendered Basic Restful Full CRUD APP
- Make a directory for the project
- Create a
server.js - Run
npm init -yto initialize a Node project in the directory -
npm install express -
npm install jsx-view-engine react react-dom method-override mongoose dotenv - Require Express in
server.js:const express = require('express') - Create an instance of an express app:
const app = express() - Make your app listen on a certain port:
app.listen(3000, () => console.log("Running on port 3000!")) - Require all modules and configure all middleware including the view engine
urlencoded, locals, methodOverride, jsx-view-engine, static - Set up
.envand.gitignorein your.envadd yourMONGO_URIand in.gitignoreignore your node_modules and .env file - Set up your models and connect your database
- Set up your dataController, viewController and routeController
- Create a
public/folder with acss/subdirectory within it - Create a
styles.cssfile within/public/cssand add your styling - Host your
public/folder:app.use('/assets', express.static('public')) - Include your CSS in the head of your
views/layout.hbsfile:<link rel="stylesheet" href="/assets/css/styles.css" /> - Build your views
Index, Show, New, Create, Edit - Add any additional needed static files to public folder
- Check for Syntax errors
- setup package.json for deployment
Part 1
JavaScript Standard Style
This is a summary of the standard JavaScript rules.
Rules
-
Use 2 spaces for indentation.
eslint:
indentfunction hello (name) { console.log('hi', name) } -
Use single quotes for strings except to avoid escaping.
eslint:
quotesconsole.log('hello there') // ✓ ok console.log("hello there") // ✗ avoid console.log(`hello there`) // ✗ avoid $("<div class='box'>") // ✓ ok console.log(`hello ${name}`) // ✓ ok -
No unused variables.
eslint:
no-unused-varsfunction myFunction () { var result = something() // ✗ avoid } -
Add a space after keywords.
eslint:
keyword-spacingif (condition) { ... } // ✓ ok if(condition) { ... } // ✗ avoid -
Add a space before a function declaration's parentheses.
eslint:
space-before-function-parenfunction name (arg) { ... } // ✓ ok function name(arg) { ... } // ✗ avoid run(function () { ... }) // ✓ ok run(function() { ... }) // ✗ avoid -
Always use
===instead of==.
Exception:obj == nullis allowed to check fornull || undefined.eslint:
eqeqeqif (name === 'John') // ✓ ok if (name == 'John') // ✗ avoidif (name !== 'John') // ✓ ok if (name != 'John') // ✗ avoid -
Infix operators must be spaced.
eslint:
space-infix-ops// ✓ ok var x = 2 var message = 'hello, ' + name + '!'// ✗ avoid var x=2 var message = 'hello, '+name+'!' -
Commas should have a space after them.
eslint:
comma-spacing// ✓ ok var list = [1, 2, 3, 4] function greet (name, options) { ... }// ✗ avoid var list = [1,2,3,4] function greet (name,options) { ... } -
Keep else statements on the same line as their curly braces.
eslint:
brace-style// ✓ ok if (condition) { // ... } else { // ... }// ✗ avoid if (condition) { // ... } else { // ... } -
For multi-line if statements, use curly braces.
eslint:
curly// ✓ ok if (options.quiet !== true) console.log('done')// ✓ ok if (options.quiet !== true) { console.log('done') }// ✗ avoid if (options.quiet !== true) console.log('done') -
Always handle the
errfunction parameter.eslint:
handle-callback-err// ✓ ok run(function (err) { if (err) throw err window.alert('done') })// ✗ avoid run(function (err) { window.alert('done') }) -
Declare browser globals with a
/* global */comment.
Exceptions are:window,document, andnavigator.
Prevents accidental use of poorly-named browser globals likeopen,length,event, andname./* global alert, prompt */ alert('hi') prompt('ok?')Explicitly referencing the function or property on
windowis okay too, though such code will not run in a Worker which usesselfinstead ofwindow.eslint:
no-undefwindow.alert('hi') // ✓ ok -
Multiple blank lines not allowed.
eslint:
no-multiple-empty-lines// ✓ ok var value = 'hello world' console.log(value)// ✗ avoid var value = 'hello world' // blank line // blank line console.log(value) -
For the ternary operator in a multi-line setting, place
?and:on their own lines.eslint:
operator-linebreak// ✓ ok var location = env.development ? 'localhost' : 'www.api.com' // ✓ ok var location = env.development ? 'localhost' : 'www.api.com' // ✗ avoid var location = env.development ? 'localhost' : 'www.api.com' -
For var declarations, write each declaration in its own statement.
eslint:
one-var// ✓ ok var silent = true var verbose = true // ✗ avoid var silent = true, verbose = true // ✗ avoid var silent = true, verbose = true -
Wrap conditional assignments with additional parentheses. This makes it clear that the expression is intentionally an assignment (
=) rather than a typo for equality (===).eslint:
no-cond-assign// ✓ ok while ((m = text.match(expr))) { // ... } // ✗ avoid while (m = text.match(expr)) { // ... } -
Add spaces inside single line blocks.
eslint:
block-spacingfunction foo () {return true} // ✗ avoid function foo () { return true } // ✓ ok -
Use camelcase when naming variables and functions.
eslint:
camelcasefunction my_function () { } // ✗ avoid function myFunction () { } // ✓ ok var my_var = 'hello' // ✗ avoid var myVar = 'hello' // ✓ ok -
Trailing commas not allowed.
eslint:
comma-danglevar obj = { message: 'hello', // ✗ avoid } -
Commas must be placed at the end of the current line.
eslint:
comma-stylevar obj = { foo: 'foo' ,bar: 'bar' // ✗ avoid } var obj = { foo: 'foo', bar: 'bar' // ✓ ok } -
Dot should be on the same line as property.
eslint:
dot-locationconsole. log('hello') // ✗ avoid console .log('hello') // ✓ ok -
Files must end with a newline.
eslint:
eol-last -
No space between function identifiers and their invocations.
eslint:
func-call-spacingconsole.log ('hello') // ✗ avoid console.log('hello') // ✓ ok -
Add space between colon and value in key value pairs.
eslint:
key-spacingvar obj = { 'key' : 'value' } // ✗ avoid var obj = { 'key' :'value' } // ✗ avoid var obj = { 'key':'value' } // ✗ avoid var obj = { 'key': 'value' } // ✓ ok -
Constructor names must begin with a capital letter.
eslint:
new-capfunction animal () {} var dog = new animal() // ✗ avoid function Animal () {} var dog = new Animal() // ✓ ok -
Constructor with no arguments must be invoked with parentheses.
eslint:
new-parensfunction Animal () {} var dog = new Animal // ✗ avoid var dog = new Animal() // ✓ ok -
Objects must contain a getter when a setter is defined.
eslint:
accessor-pairsvar person = { set name (value) { // ✗ avoid this._name = value } } var person = { set name (value) { this._name = value }, get name () { // ✓ ok return this._name } } -
Constructors of derived classes must call
super.eslint:
constructor-superclass Dog { constructor () { super() // ✗ avoid this.legs = 4 } } class Dog extends Animal { constructor () { // ✗ avoid this.legs = 4 } } class Dog extends Animal { constructor () { super() // ✓ ok this.legs = 4 } } -
Use array literals instead of array constructors.
eslint:
no-array-constructorvar nums = new Array(1, 2, 3) // ✗ avoid var nums = [1, 2, 3] // ✓ ok -
Avoid using
arguments.calleeandarguments.caller.eslint:
no-callerfunction foo (n) { if (n <= 0) return arguments.callee(n - 1) // ✗ avoid } function foo (n) { if (n <= 0) return foo(n - 1) // ✓ ok } -
Avoid modifying variables of class declarations.
eslint:
no-class-assignclass Dog {} Dog = 'Fido' // ✗ avoid -
Avoid modifying variables declared using
const.eslint:
no-const-assignconst score = 100 score = 125 // ✗ avoid -
Avoid using constant expressions in conditions (except loops).
eslint:
no-constant-conditionif (false) { // ✗ avoid // ... } if (x === 0) { // ✓ ok // ... } while (true) { // ✓ ok // ... } -
No control characters in regular expressions.
eslint:
no-control-regexvar pattern = /\x1f/ // ✗ avoid var pattern = /\x20/ // ✓ ok -
No
debuggerstatements.eslint:
no-debuggerfunction sum (a, b) { debugger // ✗ avoid return a + b } -
No
deleteoperator on variables.eslint:
no-delete-varvar name delete name // ✗ avoid -
No duplicate arguments in function definitions.
eslint:
no-dupe-argsfunction sum (a, b, a) { // ✗ avoid // ... } function sum (a, b, c) { // ✓ ok // ... } -
No duplicate name in class members.
eslint:
no-dupe-class-membersclass Dog { bark () {} bark () {} // ✗ avoid } -
No duplicate keys in object literals.
eslint:
no-dupe-keysvar user = { name: 'Jane Doe', name: 'John Doe' // ✗ avoid } -
No duplicate
caselabels inswitchstatements.eslint:
no-duplicate-caseswitch (id) { case 1: // ... case 1: // ✗ avoid } -
Use a single import statement per module.
eslint:
no-duplicate-importsimport { myFunc1 } from 'module' import { myFunc2 } from 'module' // ✗ avoid import { myFunc1, myFunc2 } from 'module' // ✓ ok -
No empty character classes in regular expressions.
eslint:
no-empty-character-classconst myRegex = /^abc[]/ // ✗ avoid const myRegex = /^abc[a-z]/ // ✓ ok -
No empty destructuring patterns.
eslint:
no-empty-patternconst { a: {} } = foo // ✗ avoid const { a: { b } } = foo // ✓ ok -
No using
eval().eslint:
no-evaleval( "var result = user." + propName ) // ✗ avoid var result = user[propName] // ✓ ok -
No reassigning exceptions in
catchclauses.eslint:
no-ex-assigntry { // ... } catch (e) { e = 'new value' // ✗ avoid } try { // ... } catch (e) { const newVal = 'new value' // ✓ ok } -
No extending native objects.
eslint:
no-extend-nativeObject.prototype.age = 21 // ✗ avoid -
Avoid unnecessary function binding.
eslint:
no-extra-bindconst name = function () { getName() }.bind(user) // ✗ avoid const name = function () { this.getName() }.bind(user) // ✓ ok -
Avoid unnecessary boolean casts.
eslint:
no-extra-boolean-castconst result = true if (!!result) { // ✗ avoid // ... } const result = true if (result) { // ✓ ok // ... } -
No unnecessary parentheses around function expressions.
eslint:
no-extra-parensconst myFunc = (function () { }) // ✗ avoid const myFunc = function () { } // ✓ ok -
Use
breakto prevent fallthrough inswitchcases.eslint:
no-fallthroughswitch (filter) { case 1: doSomething() // ✗ avoid case 2: doSomethingElse() } switch (filter) { case 1: doSomething() break // ✓ ok case 2: doSomethingElse() } switch (filter) { case 1: doSomething() // fallthrough // ✓ ok case 2: doSomethingElse() } -
No floating decimals.
eslint:
no-floating-decimalconst discount = .5 // ✗ avoid const discount = 0.5 // ✓ ok -
Avoid reassigning function declarations.
eslint:
no-func-assignfunction myFunc () { } myFunc = myOtherFunc // ✗ avoid -
No reassigning read-only global variables.
eslint:
no-global-assignwindow = {} // ✗ avoid -
No implied
eval().eslint:
no-implied-evalsetTimeout("alert('Hello world')") // ✗ avoid setTimeout(function () { alert('Hello world') }) // ✓ ok -
No function declarations in nested blocks.
eslint:
no-inner-declarationsif (authenticated) { function setAuthUser () {} // ✗ avoid } -
No invalid regular expression strings in
RegExpconstructors.eslint:
no-invalid-regexpRegExp('[a-z') // ✗ avoid RegExp('[a-z]') // ✓ ok -
No irregular whitespace.
eslint:
no-irregular-whitespacefunction myFunc () /*<NBSP>*/{} // ✗ avoid -
No using
__iterator__.eslint:
no-iteratorFoo.prototype.__iterator__ = function () {} // ✗ avoid -
No labels that share a name with an in scope variable.
eslint:
no-label-varvar score = 100 function game () { score: while (true) { // ✗ avoid score -= 10 if (score > 0) continue score break } } -
No label statements.
eslint:
no-labelslabel: while (true) { break label // ✗ avoid } -
No unnecessary nested blocks.
eslint:
no-lone-blocksfunction myFunc () { { // ✗ avoid myOtherFunc() } } function myFunc () { myOtherFunc() // ✓ ok } -
Avoid mixing spaces and tabs for indentation.
eslint:
no-mixed-spaces-and-tabs -
Do not use multiple spaces except for indentation.
eslint:
no-multi-spacesconst id = 1234 // ✗ avoid const id = 1234 // ✓ ok -
No multiline strings.
eslint:
no-multi-strconst message = 'Hello \ world' // ✗ avoid -
No
newwithout assigning object to a variable.eslint:
no-newnew Character() // ✗ avoid const character = new Character() // ✓ ok -
No using the
Functionconstructor.eslint:
no-new-funcvar sum = new Function('a', 'b', 'return a + b') // ✗ avoid -
No using the
Objectconstructor.eslint:
no-new-objectlet config = new Object() // ✗ avoid -
No using
new require.eslint:
no-new-requireconst myModule = new require('my-module') // ✗ avoid -
No using the
Symbolconstructor.eslint:
no-new-symbolconst foo = new Symbol('foo') // ✗ avoid -
No using primitive wrapper instances.
eslint:
no-new-wrappersconst message = new String('hello') // ✗ avoid -
No calling global object properties as functions.
eslint:
no-obj-callsconst math = Math() // ✗ avoid -
No octal literals.
eslint:
no-octalconst octal = 042 // ✗ avoid const decimal = 34 // ✓ ok const octalString = '042' // ✓ ok -
No octal escape sequences in string literals.
eslint:
no-octal-escapeconst copyright = 'Copyright \251' // ✗ avoid -
Avoid string concatenation when using
__dirnameand__filename.eslint:
no-path-concatconst pathToFile = __dirname + '/app.js' // ✗ avoid const pathToFile = path.join(__dirname, 'app.js') // ✓ ok -
Avoid using
__proto__. UsegetPrototypeOfinstead.eslint:
no-protoconst foo = obj.__proto__ // ✗ avoid const foo = Object.getPrototypeOf(obj) // ✓ ok -
No redeclaring variables.
eslint:
no-redeclarelet name = 'John' let name = 'Jane' // ✗ avoid let name = 'John' name = 'Jane' // ✓ ok -
Avoid multiple spaces in regular expression literals.
eslint:
no-regex-spacesconst regexp = /test value/ // ✗ avoid const regexp = /test {3}value/ // ✓ ok const regexp = /test value/ // ✓ ok -
Assignments in return statements must be surrounded by parentheses.
eslint:
no-return-assignfunction sum (a, b) { return result = a + b // ✗ avoid } function sum (a, b) { return (result = a + b) // ✓ ok } -
Avoid assigning a variable to itself
eslint:
no-self-assignname = name // ✗ avoid -
Avoid comparing a variable to itself.
eslint:
no-self-compareif (score === score) {} // ✗ avoid -
Avoid using the comma operator.
eslint:
no-sequencesif (doSomething(), !!test) {} // ✗ avoid -
Restricted names should not be shadowed.
eslint:
no-shadow-restricted-nameslet undefined = 'value' // ✗ avoid -
Sparse arrays are not allowed.
eslint:
no-sparse-arrayslet fruits = ['apple',, 'orange'] // ✗ avoid -
Tabs should not be used
eslint:
no-tabs -
Regular strings must not contain template literal placeholders.
eslint:
no-template-curly-in-stringconst message = 'Hello ${name}' // ✗ avoid const message = `Hello ${name}` // ✓ ok -
super()must be called before usingthis.eslint:
no-this-before-superclass Dog extends Animal { constructor () { this.legs = 4 // ✗ avoid super() } } -
Only
throwanErrorobject.eslint:
no-throw-literalthrow 'error' // ✗ avoid throw new Error('error') // ✓ ok -
Whitespace not allowed at end of line.
eslint:
no-trailing-spaces -
Initializing to
undefinedis not allowed.eslint:
no-undef-initlet name = undefined // ✗ avoid let name name = 'value' // ✓ ok -
No unmodified conditions of loops.
eslint:
no-unmodified-loop-conditionfor (let i = 0; i < items.length; j++) {...} // ✗ avoid for (let i = 0; i < items.length; i++) {...} // ✓ ok -
No ternary operators when simpler alternatives exist.
eslint:
no-unneeded-ternarylet score = val ? val : 0 // ✗ avoid let score = val || 0 // ✓ ok -
No unreachable code after
return,throw,continue, andbreakstatements.eslint:
no-unreachablefunction doSomething () { return true console.log('never called') // ✗ avoid } -
No flow control statements in
finallyblocks.eslint:
no-unsafe-finallytry { // ... } catch (e) { // ... } finally { return 42 // ✗ avoid } -
The left operand of relational operators must not be negated.
eslint:
no-unsafe-negationif (!key in obj) {} // ✗ avoid if (!(key in obj)) {} // ✓ ok -
Avoid unnecessary use of
.call()and.apply().eslint:
no-useless-callsum.call(null, 1, 2, 3) // ✗ avoid -
Avoid using unnecessary computed property keys on objects.
eslint:
no-useless-computed-keyconst user = { ['name']: 'John Doe' } // ✗ avoid const user = { name: 'John Doe' } // ✓ ok -
No unnecessary constructor.
eslint:
no-useless-constructorclass Car { constructor () { // ✗ avoid } } -
No unnecessary use of escape.
eslint:
no-useless-escapelet message = 'Hell\o' // ✗ avoid -
Renaming import, export, and destructured assignments to the same name is not allowed.
eslint:
no-useless-renameimport { config as config } from './config' // ✗ avoid import { config } from './config' // ✓ ok -
No whitespace before properties.
eslint:
no-whitespace-before-propertyuser .name // ✗ avoid user.name // ✓ ok -
No using
withstatements.eslint:
no-withwith (val) {...} // ✗ avoid -
Maintain consistency of newlines between object properties.
eslint:
object-property-newlineconst user = { name: 'Jane Doe', age: 30, username: 'jdoe86' // ✗ avoid } const user = { name: 'Jane Doe', age: 30, username: 'jdoe86' } // ✓ ok const user = { name: 'Jane Doe', age: 30, username: 'jdoe86' } // ✓ ok -
No padding within blocks.
eslint:
padded-blocksif (user) { // ✗ avoid const name = getName() } if (user) { const name = getName() // ✓ ok } -
No whitespace between spread operators and their expressions.
eslint:
rest-spread-spacingfn(... args) // ✗ avoid fn(...args) // ✓ ok -
Semicolons must have a space after and no space before.
eslint:
semi-spacingfor (let i = 0 ;i < items.length ;i++) {...} // ✗ avoid for (let i = 0; i < items.length; i++) {...} // ✓ ok -
Must have a space before blocks.
eslint:
space-before-blocksif (admin){...} // ✗ avoid if (admin) {...} // ✓ ok -
No spaces inside parentheses.
eslint:
space-in-parensgetName( name ) // ✗ avoid getName(name) // ✓ ok -
Unary operators must have a space after.
eslint:
space-unary-opstypeof!admin // ✗ avoid typeof !admin // ✓ ok -
Use spaces inside comments.
eslint:
spaced-comment//comment // ✗ avoid // comment // ✓ ok /*comment*/ // ✗ avoid /* comment */ // ✓ ok -
No spacing in template strings.
eslint:
template-curly-spacingconst message = `Hello, ${ name }` // ✗ avoid const message = `Hello, ${name}` // ✓ ok -
Use
isNaN()when checking forNaN.eslint:
use-isnanif (price === NaN) { } // ✗ avoid if (isNaN(price)) { } // ✓ ok -
typeofmust be compared to a valid string.eslint:
valid-typeoftypeof name === 'undefimed' // ✗ avoid typeof name === 'undefined' // ✓ ok -
Immediately Invoked Function Expressions (IIFEs) must be wrapped.
eslint:
wrap-iifeconst getName = function () { }() // ✗ avoid const getName = (function () { }()) // ✓ ok const getName = (function () { })() // ✓ ok -
The
*inyield*expressions must have a space before and after.eslint:
yield-star-spacingyield* increment() // ✗ avoid yield * increment() // ✓ ok -
Avoid Yoda conditions.
eslint:
yodaif (42 === age) { } // ✗ avoid if (age === 42) { } // ✓ ok
Semicolons
-
eslint:
semiwindow.alert('hi') // ✓ ok window.alert('hi'); // ✗ avoid -
Never start a line with
(,[,`, or a handful of other unlikely possibilities.This is the only gotcha with omitting semicolons, and
standardprotects you from this potential issue.(The full list is:
[,(,`,+,*,/,-,,,., but most of these will never appear at the start of a line in real code.)eslint:
no-unexpected-multiline// ✓ ok ;(function () { window.alert('ok') }()) // ✗ avoid (function () { window.alert('ok') }())// ✓ ok ;[1, 2, 3].forEach(bar) // ✗ avoid [1, 2, 3].forEach(bar)// ✓ ok ;`hello`.indexOf('o') // ✗ avoid `hello`.indexOf('o')Note: If you're often writing code like this, you may be trying to be too clever.
Clever short-hands are discouraged, in favor of clear and readable expressions, whenever possible.
Instead of this:
;[1, 2, 3].forEach(bar)This is strongly preferred:
var nums = [1, 2, 3] nums.forEach(bar)
Helpful reading
- An Open Letter to JavaScript Leaders Regarding Semicolons
- JavaScript Semicolon Insertion – Everything you need to know
And a helpful video:
All popular code minifiers in use today use AST-based minification, so they can handle semicolon-less JavaScript with no issues (since semicolons are not required in JavaScript).
Excerpt from "An Open Letter to JavaScript Leaders Regarding Semicolons":
[Relying on automatic semicolon insertion] is quite safe, and perfectly valid JS that every browser understands. Closure compiler, yuicompressor, packer, and jsmin all can properly minify it. There is no performance impact anywhere.
I am sorry that, instead of educating you, the leaders in this language community have given you lies and fear. That was shameful. I recommend learning how statements in JS are actually terminated (and in which cases they are not terminated), so that you can write code that you find beautiful.
In general,
\nends a statement unless:
- The statement has an unclosed paren, array literal, or object literal or ends in some other way that is not a valid way to end a statement. (For instance, ending with
.or,.)- The line is
--or++(in which case it will decrement/increment the next token.)- It is a
for(),while(),do,if(), orelse, and there is no{- The next line starts with
[,(,+,*,/,-,,,., or some other binary operator that can only be found between two tokens in a single expression.The first is pretty obvious. Even JSLint is ok with
\nchars in JSON and parenthesized constructs, and withvarstatements that span multiple lines ending in,.The second is super weird. I’ve never seen a case (outside of these sorts of conversations) where you’d want to do write
i\n++\nj, but, point of fact, that’s parsed asi; ++j, noti++; j.The third is well understood, if generally despised.
if (x)\ny()is equivalent toif (x) { y() }. The construct doesn’t end until it reaches either a block, or a statement.
;is a valid JavaScript statement, soif(x);is equivalent toif(x){}or, “If x, do nothing.” This is more commonly applied to loops where the loop check also is the update function. Unusual, but not unheard of.The fourth is generally the fud-inducing “oh noes, you need semicolons!” case. But, as it turns out, it’s quite easy to prefix those lines with semicolons if you don’t mean them to be continuations of the previous line. For example, instead of this:
foo(); [1,2,3].forEach(bar);you could do this:
foo() ;[1,2,3].forEach(bar)The advantage is that the prefixes are easier to notice, once you are accustomed to never seeing lines starting with
(or[without semis.
Part 2 A deeper dive into MongoDB & Mongoose APART From Express
Lesson Objectives
- Explain what an ODM is
- Connect to Mongo via text editor
- Create a Schema for a collection
- Create a model and save it
- find a specific model
- update a model already in the database
- remove a model already in the database
- combine actions
- Use helper functions to make your life easier
- Extend the abilities of models
- Extend the abilities of classes
- Create a schema for properties of models that are objects
- Reference other models by id
- Create actions for models to execute during database actions
- Use indexes to speed up searches
Explain what an ODM is
ODM stand for Object Document Model. It translates the documents in Mongo into upgraded JavaScript Objects that have more helpful methods and properties when used in conjunction with express.
Rather than use the Mongo shell to create, read, update and delete documents, we'll use an npm package called mongoose. Mongoose will allow us to create schemas, do validations and make it easier to interact with Mongo inside an express app.
Make a Schema
A schema will allow us to set specific keys in our objects. So if we have a key of name, we won't be able to insert other keys that don't match like firstName or names. This helps keep our data more organized and reduces the chance of errors.
We can also specify the datatypes. We can set the datatype of name to a string, age to a number, dateOfBirth to a Date, bff to a Boolean etc.
We can also make some fields required and we can set default values as well.
Here is a sample Schema, with many options. We'll be making a smaller variation of this
const articleSchema = new Schema(
{
title: { type: String, required: true, unique: true }, //can say whether we want properties to be required or unique
author: { type: String, required: true },
body: String,
comments: [{ body: String, commentDate: Date }], // can have arrays of objects with specific properties
publishDate: { type: Date, default: Date.now }, // can set defaults for properties
hidden: Boolean,
meta: {
// can have properties that are objects
votes: Number,
favs: Number,
},
},
{ timestamps: true }
);Basic Set Up
In software_classwork/unit_2
mkdir learn_mongoosecd learn_mongoosetouch app.jsnpm init -yand go through the promptsnpm i mongoose dotenvtouch tweet.js .envcode .
Set Up Mongoose
Inside app.js
- require mongoose
// Dependencies
require('dotenv').config
const mongoose = require("mongoose");
const Tweet = require("./tweet.js");- tell Mongoose where to connect with Mongo and have it connect with the sub-database
tweets(if it doesn't exist, it will be created) - set
mongoose.connectionto a shorter variable name
// Global configuration
const mongoURI = process.env.MONGO_URI;
const db = mongoose.connection;- Connect to mongo
mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true });- OPTIONAL provide error/success messages about the connections
// Connection Error/Success
// Define callback functions for various events
db.on("error", (err) => console.log(err.message + " is mongod not running?"));
db.on("open", () => console.log("mongo connected: ", mongoURI));
db.on("close", () => console.log("mongo disconnected"));- While the connection is open, we won't have control of our terminal. If we want to regain control, we have to close the connection. Let's set leave the connection open for 5 seconds to demonstrate that the app will hang and then we'll get our close message.
Otherwise we have to press control c. When we run an express app, we typically want to leave the connection open, we don't need to get control of terminal back, we just let the app run.
// Automatically close after 5 seconds
// for demonstration purposes to see that you must use `db.close()` in order to regain control of Terminal tab
setTimeout(() => {
db.close();
}, 5000);- The entire configuration for mongoose:
- Don't memorize it, just set a bookmark and refer back to this as you need it.
- note the setTimeout was just to demonstrate what
db.close()does, you don't always need it
// Dependencies
require('dotenv').config()
const mongoose = require("mongoose");
const Tweet = require("./tweet.js");
// Global Configuration
const mongoURI = process.env.MONGO_URI;
const db = mongoose.connection;
// Connect to Mongo
mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true });
// Connection Error/Success - optional but can be helpful
// Define callback functions for various events
db.on("error", (err) => console.log(err.message + " is Mongod not running?"));
db.on("open", () => console.log("mongo connected: ", mongoURI));
db.on("close", () => console.log("mongo disconnected"));Set Up Tweet Schema
In tweet.js
const mongoose = require("mongoose"); // require mongoose
const Schema = mongoose.Schema; // create a shorthand for the mongoose Schema constructor
const model = mongoose.model // shorthand for model function
// create a new Schema
// This will define the shape of the documents in the collection
// https://mongoosejs.com/docs/guide.html
const tweetSchema = new Schema(
{
title: String,
body: String,
author: String,
likes: { type: Number, default: 0 },
sponsored: { type: Boolean, default: false },
},
{ timestamps: true }
);
// Creating Tweet model : We need to convert our schema into a model-- will be stored in 'tweets' collection. Mongo does this for you automatically
// Model's are fancy constructors compiled from Schema definitions
// An instance of a model is called a document.
// Models are responsible for creating and reading documents from the underlying MongoDB Database
// from here: https://mongoosejs.com/docs/models.html
const Tweet = model("Tweet", tweetSchema);
//make this exportable to be accessed in `app.js`
module.exports = Tweet;Create a Document with Mongoose
In app.js
Let's make ourselves an object to insert into our database. When we connect with an express app, our data will be coming in as an object from the browser.
const myFirstTweet = {
title: "Deep Thoughts",
body: "Friends, I have been navel-gazing",
author: "Karolin",
};Tweet.create(myFirstTweet)
// if database transaction succeeds
.then((tweet) => {
console.log(tweet)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Let's run this with
node app.js
We should see:

Timestamps, deleted, and likes had default values, a unique _id has been generated
Every time we run node app.js it will run the code, and thus insert this object over and over again. Let's not do that. Let's comment it out.
Let's insert many more tweets
const manyTweets = [
{
title: "Deep Thoughts",
body: "Friends, I have been navel-gazing",
author: "Karolin",
},
{
title: "Sage Advice",
body: "Friends, I am vegan and so should you",
author: "Karolin",
likes: 20,
},
{
title: "Whole Reality",
body: "I shall deny friendship to anyone who does not exclusively shop at Whole Foods",
author: "Karolin",
likes: 40,
},
{
title: "Organic",
body: "Friends, I have spent $2300 to be one of the first people to own an organic smartphone",
author: "Karolin",
likes: 162,
},
{
title: "Confusion",
body: "Friends, why do you just respond with the word `dislike`? Surely you mean to click the like button?",
author: "Karolin",
likes: -100,
},
{
title: "Vespa",
body: "Friends, my Vespa has been upgraded to run on old french fry oil. Its top speed is now 11 mph",
author: "Karolin",
likes: 2,
},
{
title: "Licensed",
body: "Friends, I am now officially licensed to teach yogalates. Like this to get 10% off a private lesson",
author: "Karolin",
likes: 3,
},
{
title: "Water",
body: "Friends, I have been collecting rain water so I can indulge in locally sourced raw water. Ask me how",
author: "Karolin",
},
];Let's insert all these tweets:
Tweet.insertMany(manyTweets)
// if database transaction succeeds
.then((tweets) => {
console.log(tweets)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})node app.js
and let's comment it out so we don't insert duplicates
Find Documents with Mongoose
- Mongoose has 4 methods for this
find- genericfindById- finds by ID - great for Show routes!findOne- limits the search to the first document foundwhere- allows you to build queries, we won't cover this today
Let's find all
Tweet.find({})
// if database transaction succeeds
.then((tweets) => {
console.log(tweets)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Let's limit the fields returned, the second argument allows us to pass a string with the fields we are interested in:
Tweet.find({}, "title body")
// if database transaction succeeds
.then((tweets) => {
console.log(tweets)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Let's look for a specific tweet:
Tweet.find({ title: "Water" })
// if database transaction succeeds
.then((tweet) => {
console.log(tweet)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})We can also use advanced query options. Let's find the tweets that have 20 or more likes
Tweet.find({ likes: { $gte: 20 } })
// if database transaction succeeds
.then((tweets) => {
console.log(tweets)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Delete Documents with Mongoose
We have two copies of our first tweet and a few options to delete it
remove()danger! Will remove all instancesfindOneAndRemove()- this seems like a great choice.findByIdAndRemove()- finds by ID - great for delete routes in an express app!
Tweet.findOneAndRemove({ title: "Deep Thoughts" })
// if database transaction succeeds
.then((tweet) => {
console.log(tweet)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Update Documents with Mongoose
Finally, we have a few options for updating
update()- the most generic onefindOneAndUpdate()- Let's us find one and update itfindByIdAndUpdate()- Let's us find by ID and update - great for update/put routes in an express app!
If we want to have our updated document returned to us in the callback, we have to set an option of {new: true} as the third argument
Tweet.findOneAndUpdate(
{ title: "Vespa" },
{ sponsored: true },
{ new: true })
// if database transaction succeeds
.then((tweet) => {
console.log(tweet)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})We'll see the console.logged tweet will have the value of sponsored updated to true. Without {new: true} we would get the original unaltered tweet back.
Intermediate
We can count how many tweets we have with likes greater than 20
Tweet.countDocuments({ likes: { $gte: 20 } })
// if database transaction succeeds
.then((count) => {
console.log(count)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})We can check out all the things we can do at the Mongoose API docs
Advanced & New!
It has an updated query builder that chains much like jQuery.
Do a search, limit the number of returned queries to 2, sort them by title
Tweet.find({ likes: { $gte: 20 } }, "title -_id")
.limit(2)
.sort("title")
.exec()
// if database transaction succeeds
.then((tweets) => {
console.log(tweets)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Use helper functions to make your life easier
Mongoose's default find gives you an array of objects. But what if you know you only want one object? These convenience methods just give you one object without the usual array surrounding it.
Article.findById('5757191bce5579b805705900')
// if database transaction succeeds
.then((article) => {
console.log(article)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Article.findOne({ author : 'Matt' })
// if database transaction succeeds
.then((tweet) => {
console.log(tweet)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Article.findByIdAndUpdate(
'5757191bce5579b805705900', // id of what to update
{ $set: { author: 'Matthew' } }, // how to update it
{ new : true })
// if database transaction succeeds
.then((article) => {
console.log(article)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Article.findOneAndUpdate(
{ author: 'Matt' }, // search criteria of what to update
{ $set: { author: 'Matthew' } }, // how to update it
{ new : true }) // tells findOneAndUpdate to return modified article, not the original
// if database transaction succeeds
.then((article) => {
console.log(article)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Article.findByIdAndRemove('5757191bce5579b805705900')
// if database transaction succeeds
.then((article) => {
console.log(article)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Article.findOneAndRemove({ author : 'Matt' })
// if database transaction succeeds
.then((article) => {
console.log(article)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Extend the abilities of models
You can give all models using a specific schema extra properties and methods when creating that schema
//after initial schema declaration
articleSchema.methods.longTitle = ()=>{
return this.author + ": " + this.title;
}
//instantiate a model and then call it
const article = new Article();
console.log(article.longTitle());Extend the abilities of classes
You can create static methods for model constructor functions in their schema
//after initial schema declaration
articleSchema.statics.search = function(name){ //because we use this here, we'll need an old-fashioned function
//In this situation, it's like .find(), but it searches both title and author
return this.find({
$or : [
{ title: new RegExp(name, 'i') },
{ author: new RegExp(name, 'i') }
]
});
}
//call the static method
Article.search('Some')
// if database transaction succeeds
.then((article) => {
console.log(article)
})
// if database transaction fails
.catch((error) => {
console.log(error)
})
// close db connection either way
.finally(() => {
db.close()
})Reference other models by id
Sub docs are difficult when it comes to updating, because all duplicates must be updated. But we can reference by ID to get around this.
Single ObjectId reference
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
mongoose.connect('mongodb://urifromyouraccounthere/test');
const articleSchema = new Schema({
title: { type: String },
author: { type: Schema.Types.ObjectId, ref: 'Author' } //the author property is just an id of another object
});
const authorSchema = new Schema({
name: { type: String }
});
const Article = mongoose.model('Article', articleSchema);
const Author = mongoose.model('Author', authorSchema);
const matt = new Author({name: 'Matt'});
matt.save(()=>{
const article1 = new Article({title:'Awesome Title', author: matt._id});
article1.save(()=>{
showAll();
});
});
const showAll = ()=>{
Article.find().populate('author').exec((error, article)=>{ //dynamically switch out any ids with the objects they reference
console.log(article);
mongoose.connection.close();
})
};Arrays of ObjectId references
We can do the same with arrays of ids
const authorSchema = new Schema({
name: { type: String },
articles: [{type: Schema.Types.ObjectId, ref: 'Articles'}]
});
const Article = mongoose.model('Article', articleSchema);
const Author = mongoose.model('Author', authorSchema);
const matt = new Author({name: 'Matt'});
let article_id;
matt.save(()=>{
let article1 = new Article({title:'Awesome Title', author: matt._id});
article1.save(()=>{
article_id = article1._id;
matt.articles.push(article1);
matt.save(showAll);
});
});
var showAll = (err, author)=>{
Author.find().populate('articles').exec((err, authors)=>{ //dynamically switch out any ids with the objects they reference
console.log(authors);
mongoose.connection.close();
});
};Create actions for models to execute during database actions
We can perform actions before and after database work
articleSchema.pre('save', function(next){ //because we use this here, we'll need an old-fashioned function
console.log(this);
console.log('saving to backup database');
next();
});articleSchema.post('save', function(next){ //because we use this here, we'll need an old-fashioned function
console.log('saving complete');
});