Skip to content
Laviyx Learn

Try

Node.js Back-End Practice

Practise back-end building blocks with runnable Node.js scripts. Validation with Zod, SQL with node:sqlite, transactions, password hashing, JWTs and an HTTP server tested with fetch. Every solution has been run and shows its real output.

Intermediate7 problemsUpdated

Review first: Request Bodies and Validation, Using a Database (SQLite), Authentication with Passwords and JWT, Building an HTTP Server, Getting Started with Express

  1. 1.Validate a sign-up form with Zod

    Intermediate

    Write a Zod schema for a sign-up with name (trimmed, 2 to 50 characters), email (a valid email) and age (an integer of at least 13). Validate { name: " Kiran ", email: "kiran@example.com", age: 21 } and { name: "K", email: "kiran@", age: 12.5 }, printing the cleaned data or each error as field: message.

    Show hint

    z.object({ name: z.string().trim().min(2).max(50), email: z.email(), age: z.number().int().min(13) }) and safeParse.

    Show solution
    JavaScript
    import { z } from "zod";
    
    const Signup = z.object({
      name: z.string().trim().min(2).max(50),
      email: z.email(),
      age: z.number().int().min(13),
    });
    
    for (const input of [{ name: " Kiran ", email: "kiran@example.com", age: 21 }, { name: "K", email: "kiran@", age: 12.5 }]) {
      const result = Signup.safeParse(input);
      if (result.success) console.log("OK:", result.data);
      else for (const issue of result.error.issues) console.log(`${issue.path.join(".")}: ${issue.message}`);
    }
    Output
    OK: { name: 'Kiran', email: 'kiran@example.com', age: 21 }
    name: Too small: expected string to have >=2 characters
    email: Invalid email address
    age: Invalid input: expected int, received number
  2. 2.Group and sum with SQL

    Intermediate

    In an in-memory SQLite database, create an orders table with customer and amount, insert ("Asha", 499), ("Ravi", 1200), ("Asha", 250) and ("Meera", 80) with a prepared statement, then print each customer's total, highest first.

    Show hint

    SELECT customer, SUM(amount) AS total FROM orders GROUP BY customer ORDER BY total DESC.

    Show solution
    JavaScript
    import { DatabaseSync } from "node:sqlite";
    
    const db = new DatabaseSync(":memory:");
    db.exec("CREATE TABLE orders (customer TEXT NOT NULL, amount INTEGER NOT NULL)");
    const insert = db.prepare("INSERT INTO orders (customer, amount) VALUES (?, ?)");
    for (const [customer, amount] of [["Asha", 499], ["Ravi", 1200], ["Asha", 250], ["Meera", 80]]) insert.run(customer, amount);
    
    for (const row of db.prepare("SELECT customer, SUM(amount) AS total FROM orders GROUP BY customer ORDER BY total DESC").all()) {
      console.log(row.customer, row.total);
    }
    Output
    Ravi 1200
    Asha 749
    Meera 80
  3. 4.A transaction that rolls back

    Intermediate

    Create a wallets table with Asha (500) and Ravi (100). Write transfer(from, to, amount) that runs in a transaction, refuses to let a balance go below zero, and returns "done" or the error message. Transfer 200 from Asha to Ravi, then 1000 from Ravi to Asha, and print the balances.

    Show hint

    Use BEGIN, two UPDATEs, check the sender's new balance, then COMMIT or ROLLBACK inside try/catch.

    Show solution
    JavaScript
    import { DatabaseSync } from "node:sqlite";
    
    const db = new DatabaseSync(":memory:");
    db.exec("CREATE TABLE wallets (owner TEXT PRIMARY KEY, balance INTEGER NOT NULL)");
    db.exec("INSERT INTO wallets VALUES ('Asha', 500), ('Ravi', 100)");
    
    function transfer(from, to, amount) {
      db.exec("BEGIN");
      try {
        db.prepare("UPDATE wallets SET balance = balance - ? WHERE owner = ?").run(amount, from);
        db.prepare("UPDATE wallets SET balance = balance + ? WHERE owner = ?").run(amount, to);
        if (db.prepare("SELECT balance FROM wallets WHERE owner = ?").get(from).balance < 0) {
          throw new Error(`${from} does not have ${amount}`);
        }
        db.exec("COMMIT");
        return "done";
      } catch (err) {
        db.exec("ROLLBACK");
        return err.message;
      }
    }
    
    console.log(transfer("Asha", "Ravi", 200));
    console.log(transfer("Ravi", "Asha", 1000));
    console.log(db.prepare("SELECT owner, balance FROM wallets ORDER BY owner").all().map((r) => `${r.owner}: ${r.balance}`));
    Output
    done
    Ravi does not have 1000
    [ 'Asha: 300', 'Ravi: 300' ]
  4. 5.Hash and verify a password

    Intermediate

    Using crypto.scrypt with a random 16-byte salt, hash the password "chai-at-4pm", then verify the right and a wrong password. Print whether each matches, and whether hashing the same password twice gives the same stored value.

    Show hint

    Store salt:hash as hex; to verify, hash the attempt with the stored salt and compare with timingSafeEqual.

    Show solution
    JavaScript
    import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
    import { promisify } from "node:util";
    
    const scryptAsync = promisify(scrypt);
    
    async function hash(password) {
      const salt = randomBytes(16).toString("hex");
      return `${salt}:${(await scryptAsync(password, salt, 64)).toString("hex")}`;
    }
    
    async function verify(password, stored) {
      const [salt, hex] = stored.split(":");
      return timingSafeEqual(await scryptAsync(password, salt, 64), Buffer.from(hex, "hex"));
    }
    
    const stored = await hash("chai-at-4pm");
    console.log("right password:", await verify("chai-at-4pm", stored));
    console.log("wrong password:", await verify("chai-at-5pm", stored));
    console.log("same stored value twice:", stored === (await hash("chai-at-4pm")));
    Output
    right password: true
    wrong password: false
    same stored value twice: false
  5. 6.Sign and check a JWT

    Intermediate

    With jsonwebtoken, sign a token for user "42" with role "editor" that expires in one hour. Verify it with the correct secret and print the role, then verify it with a wrong secret and print the error name.

    Show hint

    jwt.sign({ sub: "42", role: "editor" }, secret, { expiresIn: "1h" }), then jwt.verify inside try/catch.

    Show solution
    JavaScript
    import jwt from "jsonwebtoken";
    
    const secret = "a-long-random-secret-for-practice";
    const token = jwt.sign({ sub: "42", role: "editor" }, secret, { expiresIn: "1h" });
    
    console.log("role:", jwt.verify(token, secret).role);
    try {
      jwt.verify(token, "wrong-secret");
    } catch (err) {
      console.log("wrong secret:", err.name);
    }
    Output
    role: editor
    wrong secret: JsonWebTokenError
  6. 7.A server tested with fetch

    Intermediate

    Create an Express app with GET /api/square/:n that returns { n, square } and a 400 error for non-numbers. Start it on port 3100, call it with fetch for 7 and abc, print each status and body, and close the server.

    Show hint

    const server = app.listen(3100, async () => { ...; server.close(); }).

    Show solution
    JavaScript
    import express from "express";
    
    const app = express();
    app.get("/api/square/:n", (req, res) => {
      const n = Number(req.params.n);
      if (Number.isNaN(n)) return res.status(400).json({ error: "n must be a number" });
      res.json({ n, square: n * n });
    });
    
    const server = app.listen(3100, async () => {
      for (const value of ["7", "abc"]) {
        const res = await fetch(`http://localhost:3100/api/square/${value}`);
        console.log(res.status, JSON.stringify(await res.json()));
      }
      server.close();
    });
    Output
    200 {"n":7,"square":49}
    400 {"error":"n must be a number"}