Skip to content

ringku1/backend-notes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

🚀 Backend Development Notes

Personal reference guide covering Node.js, Express, EJS, REST, SQL, MongoDB, Mongoose, Authentication, Authorization, and MVC architecture.


📚 Table of Contents

  1. Node.js
  2. Express.js
  3. EJS Templating
  4. GET & POST Requests
  5. JavaScript OOP
  6. REST & CRUD
  7. SQL Fundamentals
  8. SQL with Node.js (MySQL2)
  9. MongoDB
  10. Mongoose
  11. Error Handling & Validation
  12. Authentication with Passport.js
  13. Authorization
  14. MVC Architecture

1. Node.js

Node.js is an open-source, cross-platform runtime that executes JavaScript outside the browser using the V8 engine. It's event-driven and non-blocking, making it ideal for scalable network applications.

Installation

Download from nodejs.org and verify:

node -v
npm -v

REPL (Read-Eval-Print Loop)

Quick way to test JavaScript expressions interactively:

node
> 2 + 3
5
> const msg = "Hello, Node!"
> msg
'Hello, Node!'

Exit with Ctrl+C twice or .exit.

Running a File

// hello.js
console.log("Hello, Node.js!");
node hello.js

The process Object

console.log(process.pid);       // Process ID
console.log(process.version);   // Node.js version
console.log(process.platform);  // OS platform

process.argv — Command-Line Arguments

// node myScript.js arg1 arg2
console.log(process.argv);
// ['node', 'myScript.js', 'arg1', 'arg2']

CommonJS Modules (require / exports)

// math.js
module.exports = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
};
// main.js
const math = require('./math');
console.log(math.add(5, 3));       // 8
console.log(math.subtract(10, 4)); // 6

Directory Exports via index.js

// utils/index.js
exports.math = require('./math');
exports.strings = require('./strings');
// main.js
const utils = require('./utils');
console.log(utils.math.add(7, 2));

ES Modules (import / export)

Requires "type": "module" in package.json:

// math.js
export function sum(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
// main.js
import { sum } from './math.js';
console.log(sum(5, 3)); // 8

npm Basics

Command Description
npm init -y Initialize a new project
npm install <pkg> Install a local package
npm install -g <pkg> Install globally
npm uninstall <pkg> Remove a package

package.json tracks your dependencies and scripts. Commit it — never commit node_modules/.


2. Express.js

Express is a minimal, unopinionated web framework for Node.js.

Quick Setup

npm init -y
npm install express
// app.js
const express = require('express');
const app = express();
const PORT = 3000;

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
node app.js

💡 Use nodemon for auto-restart during development: npm install -g nodemonnodemon app.js

Route Handling

app.get('/', (req, res) => res.send('Home Page'));
app.post('/submit', (req, res) => res.send('Form submitted'));
app.use('/about', (req, res) => res.send('About Page'));

// 404 catch-all
app.get('*', (req, res) => res.status(404).send('Page not found'));

Sending Responses

res.send('<h1>Hello!</h1>');          // HTML
res.json({ message: 'Hello JSON' });  // JSON
res.redirect('/other-route');         // Redirect
res.status(404).send('Not Found');    // With status code

Path Parameters & Query Strings

// Path param: /users/42
app.get('/users/:id', (req, res) => {
  const { id } = req.params;
  res.send(`User ID: ${id}`);
});

// Query string: /search?q=node
app.get('/search', (req, res) => {
  const { q } = req.query;
  res.send(`Search: ${q}`);
});

Router (Modular Routes)

// routes/users.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => res.send('All users'));
router.get('/:id', (req, res) => res.send(`User ${req.params.id}`));

module.exports = router;
// app.js
const userRoutes = require('./routes/users');
app.use('/users', userRoutes);

Middleware

Middleware functions run between the request and response. Use next() to pass control forward.

// Custom middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Built-in: parse JSON and form data
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Serve static files from /public
app.use(express.static('public'));

method-override

HTML forms only support GET/POST. Use this package to fake PUT/PATCH/DELETE:

npm install method-override
const methodOverride = require('method-override');
app.use(methodOverride('_method'));
<form method="POST" action="/posts/42?_method=DELETE">
  <button>Delete</button>
</form>

3. EJS Templating

EJS (Embedded JavaScript) lets you generate dynamic HTML from your server.

npm install ejs
const path = require('path');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

EJS Tags

Tag Purpose
<%= value %> Output (HTML-escaped)
<%- value %> Output (unescaped HTML)
<% code %> Execute JS (no output)
<%# comment %> Comment (not rendered)
<%- include('file') %> Include partial

Passing Data to Views

app.get('/profile', (req, res) => {
  res.render('profile', { username: 'alice', posts: [...] });
});
<h1>Welcome, <%= username %>!</h1>

<% if (posts.length > 0) { %>
  <ul>
    <% posts.forEach(post => { %>
      <li><%= post.title %></li>
    <% }); %>
  </ul>
<% } else { %>
  <p>No posts yet.</p>
<% } %>

Includes (Partials)

<%- include('partials/navbar') %>
<h1>Main content</h1>
<%- include('partials/footer') %>

4. GET & POST Requests

Key Differences

GET POST
Data location URL query string Request body
Use case Fetch / read data Submit / modify data
Security Visible in URL Not visible in URL
Payload size Limited Large payloads OK

Handling POST

app.use(express.urlencoded({ extended: true }));

app.post('/register', (req, res) => {
  const { username, password } = req.body;
  res.send(`Welcome, ${username}!`);
});
<form method="POST" action="/register">
  <input name="username" placeholder="Username">
  <input name="password" type="password" placeholder="Password">
  <button>Submit</button>
</form>

5. JavaScript OOP

Prototypes

Every object has a prototype. Methods added to a prototype are shared across all instances.

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Person.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
};

const john = new Person('John', 'Doe');
console.log(john.getFullName()); // John Doe

Factory Functions

function createPerson(firstName, lastName) {
  return {
    firstName,
    lastName,
    getFullName() { return `${this.firstName} ${this.lastName}`; },
  };
}

ES6 Classes

class Animal {
  constructor(name) { this.name = name; }
  speak() { console.log(`${this.name} makes a sound.`); }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
  speak() { console.log(`${this.name} (${this.breed}) barks.`); }
}

const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // Buddy (Golden Retriever) barks.

💡 extends sets up inheritance. super() calls the parent constructor — always required before using this in a subclass.


6. REST & CRUD

REST (Representational State Transfer) is an architectural style for designing web APIs. RESTful APIs use HTTP methods to perform CRUD operations on resources.

HTTP Methods → CRUD

HTTP Method CRUD Action Example Route
GET Read GET /posts
POST Create POST /posts
PUT / PATCH Update PATCH /posts/:id
DELETE Delete DELETE /posts/:id

REST Principles

  • Stateless — Each request carries all required info; server holds no client state.
  • Uniform Interface — Consistent URL structure and HTTP methods.
  • Client-Server — Frontend and backend are decoupled.
  • Resource-Based — Resources are identified by URIs.

CRUD Example (in-memory)

const { v4: uuidv4 } = require('uuid');
let posts = [];

// Create
app.post('/posts', (req, res) => {
  const { username, content } = req.body;
  posts.push({ id: uuidv4(), username, content });
  res.redirect('/posts');
});

// Read all
app.get('/posts', (req, res) => {
  res.render('index', { posts });
});

// Read one
app.get('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === req.params.id);
  res.render('show', { post });
});

// Update
app.patch('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === req.params.id);
  post.content = req.body.content;
  res.redirect('/posts');
});

// Delete
app.delete('/posts/:id', (req, res) => {
  posts = posts.filter(p => p.id !== req.params.id);
  res.redirect('/posts');
});

7. SQL Fundamentals

SQL vs. NoSQL

SQL NoSQL
Structure Relational tables Documents, key-value, graph
Schema Fixed Flexible
Best for Complex queries, transactions Fast iteration, flexible data

Core SQL Commands

-- Database
CREATE DATABASE IF NOT EXISTS mydb;
USE mydb;
DROP DATABASE mydb;
SHOW DATABASES;

-- Table
CREATE TABLE employees (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  email VARCHAR(100) UNIQUE,
  salary DECIMAL(10,2) DEFAULT 0,
  hired_at DATE
);

ALTER TABLE employees ADD COLUMN phone VARCHAR(15);
ALTER TABLE employees DROP COLUMN phone;
ALTER TABLE employees RENAME TO staff;
TRUNCATE TABLE employees;  -- removes all rows, keeps table
DROP TABLE employees;      -- removes table entirely

CRUD in SQL

-- Insert
INSERT INTO employees (name, email, salary)
VALUES ('Alice', 'alice@example.com', 60000);

-- Read
SELECT * FROM employees;
SELECT name, salary FROM employees WHERE salary > 50000;

-- Update
UPDATE employees SET salary = 65000 WHERE name = 'Alice';

-- Delete
DELETE FROM employees WHERE id = 1;

Filtering & Aggregation

-- WHERE with operators: =, <>, >, <, >=, <=, AND, OR, NOT
SELECT * FROM employees WHERE salary > 50000 AND department = 'Engineering';

-- Aggregates
SELECT AVG(salary), MAX(salary), COUNT(*) FROM employees;

-- Group and filter
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
HAVING AVG(salary) > 50000
ORDER BY avg_sal DESC;

Constraints

-- PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, DEFAULT
CREATE TABLE orders (
  id INT PRIMARY KEY,
  customer_id INT,
  status VARCHAR(20) DEFAULT 'Pending',
  amount DECIMAL(10,2) CHECK (amount >= 0),
  FOREIGN KEY (customer_id) REFERENCES customers(id)
);

Joins

-- INNER JOIN: only matching rows
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;

-- LEFT JOIN: all from left table + matching from right
SELECT e.name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

Query Clause Order

SELECTFROMJOINWHEREGROUP BYHAVINGORDER BYLIMIT

8. SQL with Node.js (MySQL2)

npm install mysql2 express ejs method-override @faker-js/faker

Connect to MySQL

const mysql = require('mysql2');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'yourpassword',
  database: 'mydb',
});

Query Examples

// Read all users
app.get('/users', (req, res) => {
  connection.query('SELECT * FROM users', (err, users) => {
    if (err) throw err;
    res.render('users', { users });
  });
});

// Update user (with password check)
app.patch('/users/:id', (req, res) => {
  const { id } = req.params;
  const { newUsername, password } = req.body;

  connection.query('SELECT * FROM users WHERE id = ?', [id], (err, results) => {
    if (err) throw err;
    const user = results[0];
    if (user.password !== password) return res.send('Wrong password');

    connection.query('UPDATE users SET username = ? WHERE id = ?',
      [newUsername, id], (err) => {
        if (err) throw err;
        res.redirect('/users');
      });
  });
});

// Delete user
app.delete('/users/:id', (req, res) => {
  connection.query('DELETE FROM users WHERE id = ?', [req.params.id], (err) => {
    if (err) throw err;
    res.redirect('/users');
  });
});

⚠️ Always use parameterized queries (? placeholders) to prevent SQL injection.


9. MongoDB

MongoDB is a NoSQL document database. It stores data as BSON (Binary JSON) in collections of documents.

Connect via mongosh

mongosh

Core Shell Commands

show dbs                          # list all databases
use mydb                          # switch / create database
show collections                  # list collections in current db
db.dropDatabase()                 # delete current database

CRUD in Shell

# Insert
db.users.insertOne({ name: "Alice", age: 25 })
db.users.insertMany([{ name: "Bob" }, { name: "Carol" }])

# Find
db.users.find()
db.users.find({ age: { $gte: 25 } })
db.users.findOne({ name: "Alice" })

# Update
db.users.updateOne({ name: "Alice" }, { $set: { age: 26 } })
db.users.updateMany({ age: { $lt: 18 } }, { $set: { status: "minor" } })

# Delete
db.users.deleteOne({ name: "Alice" })
db.users.deleteMany({ age: { $lt: 18 } })

Query Operators

Operator Meaning
$eq Equals
$gt / $gte Greater than / or equal
$lt / $lte Less than / or equal
$in Value in array
$and Logical AND
$or Logical OR
$not Logical NOT
db.users.find({ $or: [{ age: { $lt: 18 } }, { name: "Admin" }] })

Nested Documents

{
  name: "John",
  address: {
    street: "123 Main St",
    city: "New York",
    zip: "10001"
  }
}

10. Mongoose

Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It provides schema-based modeling, validation, and middleware.

npm install mongoose

Connect

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb')
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error(err));

Define a Schema & Model

const { Schema, model } = require('mongoose');

const listingSchema = new Schema({
  title: { type: String, required: true },
  description: String,
  price: { type: Number, min: 0 },
  location: String,
  country: String,
  createdAt: { type: Date, default: Date.now },
});

module.exports = model('Listing', listingSchema);

CRUD with Mongoose

const Listing = require('./models/Listing');

// Create
const listing = new Listing({ title: 'Beach House', price: 200 });
await listing.save();
// or: await Listing.create({ title: 'Beach House', price: 200 });

// Read
const all = await Listing.find();
const one = await Listing.findById(id);

// Update
await Listing.findByIdAndUpdate(id, { price: 250 }, { new: true });

// Delete
await Listing.findByIdAndDelete(id);

Schema Validation

const userSchema = new Schema({
  username: { type: String, required: true, minlength: 3 },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 0, max: 120 },
});

Mongoose Middleware (Hooks)

Run code before or after operations:

// Auto-delete reviews when a listing is deleted
listingSchema.post('findOneAndDelete', async (listing) => {
  if (listing) {
    await Review.deleteMany({ _id: { $in: listing.reviews } });
  }
});

Population (References Between Collections)

const listingSchema = new Schema({
  title: String,
  owner: { type: Schema.Types.ObjectId, ref: 'User' },
  reviews: [{ type: Schema.Types.ObjectId, ref: 'Review' }],
});

// Populate owner when querying
const listing = await Listing.findById(id).populate('owner').populate('reviews');

Joi Validation (Request Body Validation)

npm install joi
const Joi = require('joi');

const listingSchema = Joi.object({
  listing: Joi.object({
    title: Joi.string().required(),
    price: Joi.number().min(0).required(),
    location: Joi.string().required(),
  }).required(),
});

// Middleware
const validateListing = (req, res, next) => {
  const { error } = listingSchema.validate(req.body);
  if (error) throw new ExpressError(400, error.details[0].message);
  next();
};

11. Error Handling & Validation

Custom Error Class

// utils/ExpressError.js
class ExpressError extends Error {
  constructor(statusCode, message) {
    super();
    this.statusCode = statusCode;
    this.message = message;
  }
}
module.exports = ExpressError;

Async Error Wrapper

Avoids repetitive try/catch in every async route:

// utils/wrapAsync.js
module.exports = (fn) => (req, res, next) => {
  fn(req, res, next).catch(next);
};
// Usage in routes
router.get('/:id', wrapAsync(async (req, res) => {
  const listing = await Listing.findById(req.params.id);
  if (!listing) throw new ExpressError(404, 'Listing not found');
  res.render('listings/show', { listing });
}));

Global Error Handler

Put this last in app.js:

app.use((err, req, res, next) => {
  const { statusCode = 500, message = 'Something went wrong' } = err;
  res.status(statusCode).render('error', { message });
});

12. Authentication with Passport.js

Authentication = verifying who the user is.

npm install passport passport-local passport-local-mongoose express-session connect-flash

Session Setup

const session = require('express-session');
const flash = require('connect-flash');

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, // 1 week
}));

app.use(flash());

Passport Setup

const passport = require('passport');
const LocalStrategy = require('passport-local');

app.use(passport.initialize());
app.use(passport.session());

passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

User Model with passport-local-mongoose

const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
});

userSchema.plugin(passportLocalMongoose); // adds username, hash, salt + helpers
module.exports = mongoose.model('User', userSchema);

Auth Routes

const User = require('../models/User');

// Register
router.post('/register', wrapAsync(async (req, res) => {
  const { username, email, password } = req.body;
  const user = new User({ username, email });
  const registered = await User.register(user, password);
  req.login(registered, err => {
    if (err) return next(err);
    req.flash('success', 'Welcome!');
    res.redirect('/listings');
  });
}));

// Login
router.post('/login', passport.authenticate('local', {
  failureRedirect: '/login',
  failureFlash: true,
}), (req, res) => {
  req.flash('success', `Welcome back, ${req.user.username}!`);
  res.redirect('/listings');
});

// Logout
router.post('/logout', (req, res, next) => {
  req.logout(err => {
    if (err) return next(err);
    req.flash('success', 'Logged out successfully');
    res.redirect('/listings');
  });
});

isLoggedIn Middleware

module.exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) return next();
  req.session.redirectUrl = req.originalUrl;
  req.flash('error', 'Please log in to continue');
  res.redirect('/login');
};

Flash Messages in Views

// Make flash & current user available in all templates
app.use((req, res, next) => {
  res.locals.success = req.flash('success');
  res.locals.error = req.flash('error');
  res.locals.currUser = req.user;
  next();
});
<% if (success.length) { %>
  <div class="alert alert-success"><%= success %></div>
<% } %>
<% if (error.length) { %>
  <div class="alert alert-danger"><%= error %></div>
<% } %>

13. Authorization

Authorization = verifying what a logged-in user is allowed to do.

isOwner Middleware (Server-side)

module.exports.isOwner = async (req, res, next) => {
  const { id } = req.params;
  const listing = await Listing.findById(id);
  if (!listing.owner._id.equals(res.locals.currUser._id)) {
    req.flash('error', 'You are not the owner of this listing');
    return res.redirect(`/listings/${id}`);
  }
  next();
};
// Protect edit/delete routes
router.delete('/:id', isLoggedIn, isOwner, wrapAsync(async (req, res) => {
  await Listing.findByIdAndDelete(req.params.id);
  req.flash('success', 'Listing deleted');
  res.redirect('/listings');
}));

Client-side Authorization (UI)

Only show edit/delete buttons to the owner — but never rely on this alone:

<% if (currUser && listing.owner._id.equals(currUser._id)) { %>
  <a href="/listings/<%= listing._id %>/edit">Edit</a>
  <form method="POST" action="/listings/<%= listing._id %>?_method=DELETE">
    <button>Delete</button>
  </form>
<% } %>

⚠️ Client-side authorization is UX only. Server-side middleware is the real security layer.


14. MVC Architecture

MVC (Model-View-Controller) separates concerns into three layers, making apps easier to scale and maintain.

Layer Responsibility
Model Data, schema, DB interactions
View HTML templates (EJS), what the user sees
Controller Business logic, connects Model ↔ View

Directory Structure

project/
├── controllers/
│   ├── listings.js
│   └── users.js
├── models/
│   ├── listing.js
│   ├── review.js
│   └── user.js
├── routes/
│   ├── listing.js
│   └── user.js
├── views/
│   ├── listings/
│   └── layouts/
├── utils/
│   ├── ExpressError.js
│   └── wrapAsync.js
├── middleware.js
└── app.js

Model (models/listing.js)

const mongoose = require('mongoose');
const Review = require('./review');

const listingSchema = new mongoose.Schema({
  title: { type: String, required: true },
  price: Number,
  owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  reviews: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Review' }],
});

// Cascade delete reviews when listing is deleted
listingSchema.post('findOneAndDelete', async (listing) => {
  if (listing) await Review.deleteMany({ _id: { $in: listing.reviews } });
});

module.exports = mongoose.model('Listing', listingSchema);

Controller (controllers/listings.js)

const Listing = require('../models/listing');

module.exports.index = async (req, res) => {
  const allListings = await Listing.find({});
  res.render('listings/index', { allListings });
};

module.exports.create = async (req, res) => {
  const listing = new Listing(req.body.listing);
  listing.owner = req.user._id;
  await listing.save();
  req.flash('success', 'New listing created!');
  res.redirect('/listings');
};

module.exports.destroy = async (req, res) => {
  await Listing.findByIdAndDelete(req.params.id);
  req.flash('success', 'Listing deleted');
  res.redirect('/listings');
};

Router with router.route() (routes/listing.js)

router.route() groups multiple HTTP methods under the same path for cleaner code:

const express = require('express');
const router = express.Router();
const { isLoggedIn, isOwner, validateListing } = require('../middleware');
const listingController = require('../controllers/listings');
const wrapAsync = require('../utils/wrapAsync');
const multer = require('multer');
const { storage } = require('../cloudConfig');
const upload = multer({ storage });

router.route('/')
  .get(wrapAsync(listingController.index))
  .post(isLoggedIn, upload.single('listing[image]'), validateListing, wrapAsync(listingController.create));

router.route('/:id')
  .get(wrapAsync(listingController.show))
  .patch(isLoggedIn, isOwner, upload.single('listing[image]'), validateListing, wrapAsync(listingController.update))
  .delete(isLoggedIn, isOwner, wrapAsync(listingController.destroy));

module.exports = router;

Wiring it all together (app.js)

const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const flash = require('connect-flash');
const passport = require('passport');
const LocalStrategy = require('passport-local');
const methodOverride = require('method-override');
const path = require('path');

const User = require('./models/user');
const listingRoutes = require('./routes/listing');
const userRoutes = require('./routes/user');

const app = express();

mongoose.connect('mongodb://localhost:27017/mydb');

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: true }));
app.use(methodOverride('_method'));
app.use(express.static(path.join(__dirname, 'public')));

app.use(session({ secret: 'mysecret', resave: false, saveUninitialized: false }));
app.use(flash());

app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

// Make flash + user available in all templates
app.use((req, res, next) => {
  res.locals.success = req.flash('success');
  res.locals.error = req.flash('error');
  res.locals.currUser = req.user;
  next();
});

app.use('/listings', listingRoutes);
app.use('/', userRoutes);

// Global error handler
app.use((err, req, res, next) => {
  const { statusCode = 500, message = 'Something went wrong' } = err;
  res.status(statusCode).render('error', { message });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Notes by me — built while learning the MERN/Node stack. Update as needed. Source: https://github.com/MaanasSehgal/Backend-Notes

About

Backend notes with NodeJS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors