munotes®

Mini Project I (Real-World Application Development) Notes | B.Sc. (Computer Science) Semester 5 | Mumbai University | munotes

Get access to whole semester resourcesSemester Pass

Official Notes munotes.in

Mini Project I (Real-World Application Development)

B.SC. (COMPUTER SCIENCE) · SEMESTER 5

Strictly as per the University of Mumbai NEP syllabus in force for B.Sc. (Computer Science)

For B.Sc. (Computer Science) students of the University of Mumbai and all its affiliated colleges

Open the book ↓

munotes.in Third Year

Mini Project I (Real-World Application Development)

Copyright © 2026 munotes.in. All rights reserved.

Written and first published by munotes.in, 2026.

This book is free for individual students to read at munotes.in. No part of it may be reproduced, distributed, stored, translated or used for institutional or classroom purposes in any form without a prior written licence from munotes.in.

Licensing and permissions: contact@munotes.in

The text of statutes and of judgments reproduced in this book is in the public domain under section 52(1)(q) of the Copyright Act 1957. The commentary, arrangement, examples and questions are the original work of munotes.in.

munotes.in is an independent study resource for MU students. It is not affiliated with, endorsed by, or officially connected to the University of Mumbai. Course names and university references describe the students and syllabus the material relates to.

munotes.in

Contents

Module I Problem Identification, Requirement Engineering & System Design Phase

  1. How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each 1
  2. The Design Phase at a Glance, and the Worked Project in This Book 8
  3. Finding a Real-World Problem: Industry, Social and Institutional 13
  4. Problem Justification and Scope Definition 20
  5. Stakeholder Identification 26
  6. Technical Feasibility 32
  7. Economic Feasibility 38
  8. Operational Feasibility and the Feasibility Report 43
  9. Requirement Engineering: Eliciting, Recording and Checking Requirements 50
  10. Functional Requirements Specification 57
  11. Non-Functional Requirements 63
  12. Use-Case Analysis 68
  13. Requirement Prioritization 74
  14. Constraints and Assumptions 79
  15. Selecting an SDLC Model for a Mini Project 84
  16. The Work Breakdown Structure 89
  17. The Project Timeline: Estimates, Dependencies and the Gantt Chart 95
  18. Resource Planning 104
  19. System Modeling with UML: the Six Diagrams and How They Fit Together 112
  20. The Use Case Diagram 119
  21. The Class Diagram 125
  22. The Sequence Diagram 132
  23. The Activity Diagram 138
  24. The ER Diagram 144
  25. The Deployment Diagram 150
  26. System Architecture Design: Styles, Layers and Choosing the Stack 154
  27. Frontend Architecture 160
  28. Backend Architecture 167
  29. Database Schema Design 174
  30. API Structure 181
  31. Security Considerations 187
  32. The Module 1 Documents: What Is Due and How They Connect 195
  33. The Project Proposal 200
  34. The SRS Document 207
  35. The Complete UML Set 217
  36. The Architecture Design Document 222
  37. The Design Review: Presenting Module 1 to Your Guide 239

Module II Implementation, Testing, Deployment & Evaluation Phase

  1. The Build Phase at a Glance 246
  2. Setting Up the Development Machine: Node.js, MySQL, an Editor and Git 251
  3. Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server 259
  4. Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter 270
  5. Backend Implementation, Part 1: the Express Application and Its Routes 281
  6. Backend Implementation, Part 2: the Services and the Business Rules 291
  7. Database Integration, Part 1: the Connection Pool and Safe Queries 299
  8. Database Integration, Part 2: Transactions and the Order That Must Not Oversell 308
  9. Authentication: Passwords, Sessions and Roles 315
  10. Validation: Checking Every Input on the Server 323
  11. Error Handling 331
  12. Development Progress and Code Review 337
  13. Testing the Project: the Levels, the Test Plan and the Tools 344
  14. Unit Testing 350
  15. Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables 357
  16. Integration Testing 363
  17. System Testing and Acceptance 373
  18. Test Case Preparation 378
  19. Bug Tracking 384
  20. Local Hosting: Running the Application for the Whole Lab 388
  21. Cloud Deployment 393
  22. APK Build: Putting the Application on an Android Phone 398
  23. Server Configuration: Linux, Nginx, systemd and HTTPS 404
  24. Version Control Using GitHub, Part 1: the Repository, Commits and the Remote 410
  25. Version Control Using GitHub, Part 2: Branches, Pull Requests and Releases 415
  26. Basic Load Testing 420
  27. Input Validation Checks 426
  28. Security Validation 432
  29. The Technical Report 437
  30. The User Manual 444
  31. Screenshots 451
  32. Source Code Documentation 456
  33. The Final Deliverables: What Is Due at the End of Module 2 463
  34. The Working Application 467
  35. The GitHub Repository 472
  36. The Final Report 477
  37. Presentation and Demonstration 482
  38. The External Evaluation: How the Thirty Marks Are Earned 487
  39. The Viva Voce: Questions You Must Be Able to Answer 492
munotes.in

Module I

Problem Identification, Requirement Engineering & System Design Phase

munotes.in

Chapter One

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

Syllabus topic MU's EVALUATION SCHEME, section C, "Evaluation for Mini Project (2 Credit Courses)", Mini Project I, with the scheme's rule "Individual Passing in Internal and External Examination" and its letter grades. None of this is printed inside the paper's own block.

In one line

Mini Project I has no written examination at all: your project guide gives you up to 20 marks during the semester for four things you show them, an external examiner gives you up to 30 marks at the end for four more, and you must pass each of those two halves on its own.

In the wording to use when asked: Mini Project I (Real-World Application Development) is a 2-credit practical course of 50 marks, assessed 40 per cent internally by the project guide and 60 per cent externally by an external examiner, under the University's rule of individual passing, at 40 per cent in each component.

What kind of paper this is

Most papers you have sat at university end in a question paper. You read questions in a hall and write answers. This one does not. Nothing in MU's scheme for this paper is a question you answer on paper. What is assessed is a project you build over the semester, the documents you write about it, and your command of it when you show it and are questioned on it.

MU's particulars for the paper say it all in a few rows:

HeadingWhat MU prints
VerticalMajor (Mandatory)
TypePractical
Credits2 credits (1 credit = 30 Hours of Practical work in a semester)
Hours allotted60 hours
Marks allotted50 Marks
AssessmentInternal Continuous Assessment: 40% Semester End Examination: 60%

Two things in that table matter more than they look.

It is a Major, and it is Mandatory. Every TY B.Sc. (Computer Science) student under this scheme does it, whatever elective they chose. It counts in your semester result exactly as a theory Major of 2 credits does.

The 60 hours are practical work. One credit is 30 hours of practical work, so two credits are 60. MU splits them evenly: Module 1 is printed as 30 hours and Module 2 as 30 hours. Module 1 is the design phase, and it ends in four documents. Module 2 is the build phase, and it ends in a working application, a GitHub repository, a final report and a presentation.

There is no text book and no reference book printed for this paper. Its block in the syllabus simply stops after the assessment row. That is not an oversight you need to fill by buying something. The paper draws on what you have already studied: MU's own description of it says it "integrates knowledge from Software Engineering, Database Management Systems, Web Development, Mobile Application Development, Cloud Computing, Cyber Security, and Data Structures". This book teaches everything the paper asks you to do, from the first idea to the last question in the viva.

munotes.in1

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

The fifty marks in one table

MU prints the marks of this paper in the EVALUATION SCHEME at the back of the syllabus, under section C, "Evaluation for Mini Project (2 Credit Courses)". There are eight components, four for each assessor.

Internal evaluation, to be assessed by the Project Guide:

ComponentMarks
Problem Identification & Project Proposal5
System Design (SRS, UML Diagrams, Architecture)5
Development Progress & Code Review5
Internal Presentation / Review5
Total20

External evaluation, to be assessed by the External Examiner:

ComponentMarks
Working Application Demonstration10
Technical Design & Implementation10
Project Report Evaluation5
Viva Voce5
Total30

The component names above are MU's own, copied as printed, ampersands included. When your guide or examiner uses one of these names, this is what they are referring to.

Look at where the weight falls. Twenty of the fifty marks, 40 per cent, are the working application and its technical design and implementation. A project that is well documented but does not run, or runs but was never really designed, cannot score well however good the report is. Equally, the four internal components are each worth as much as the report: the guide's marks are not a formality.

Who awards them, and when

The Project Guide is the teacher your college assigns to supervise your project. MU gives them all 20 internal marks. They see your work throughout the semester, which is why their four components are about the stages of the work: the problem and the proposal, the design, the progress of the code, and a presentation.

The External Examiner is a teacher from outside your college, appointed for the examination, who sees your project once, at the end. MU gives them all 30 external marks, for what can be judged at that one meeting: the application running, its design and code, the report, and your answers.

MU does not print the dates on which each internal component is assessed. It prints only what they are. So the timing is your college's, and in practice it follows the work:

ComponentThe earliest point it can be judged
Problem Identification & Project Proposalonce the proposal is written, early in Module 1
System Design (SRS, UML Diagrams, Architecture)once the SRS, the UML set and the architecture document exist, at the end of Module 1
Development Progress & Code Reviewwhile Module 2 is being built
Internal Presentation / Reviewwhen there is something to present, usually near the end
The four external componentson the day of the external examination

Ask your guide in the first week when they will assess each of the four. Write the dates in your project plan. Chapter 17 shows how.

munotes.in2

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

Passing: each half on its own

This is the rule that catches students out. The scheme page of MU's syllabus, which applies to every course of this programme, prints the scheme of examination in three lines, "40% Internal", "60% External, Semester End Examination" and "Individual Passing in Internal and External Examination", and the standard of passing as "40% in each component".

Individual passing means the internal marks and the external marks are passed separately. It is not enough for the total to reach 40 per cent. For this paper:

  • 40 per cent of the internal 20 marks is 8. You need at least 8 of 20.
  • 40 per cent of the external 30 marks is 12. You need at least 12 of 30.

A worked example makes the trap plain. Three students of one college finish the paper with these marks:

StudentInternal (of 20)External (of 30)Total (of 50)Passes?
Rohit171128No: 11 is below 12
Sana72633No: 7 is below 8
Irfan91322Yes: 9 and 13 both pass

Rohit has 28 of 50, which is 56 per cent, and has not passed, because his external marks are one short of 12. Sana has 66 per cent and has not passed either, because her internal marks are one short of 8. Irfan has only 44 per cent and has passed, because each half cleared its own bar. Guard both halves. A strong external day cannot rescue internal marks you let slip during the semester.

From marks to a grade

The same appendix prints how a percentage becomes a letter grade and a grade point. Your result in this paper is the percentage of 50 that your two halves add up to:

Per cent of marksLetter gradeGrade point
90.0 to 100O (Outstanding)10
80.0 to below 90.0A+ (Excellent)9
70.0 to below 80.0A (Very Good)8
60.0 to below 70.0B+ (Good)7
55.0 to below 60.0B (Above Average)6
50.0 to below 55.0C (Average)5
40.0 to below 50.0P (Pass)4
Below 40.0F (Fail)0

So a student with 18 internal and 24 external has 42 of 50. 42 divided by 50 is 0.84, which is 84 per cent, which is A+ (Excellent) with 9 grade points. Each mark on a 50-mark paper is worth 2 per cent, so the difference between A and A+ can be a single mark.

What does not apply to this paper

The other practical papers of your semester, such as Computer Science Practical 5, are assessed under section B of the same appendix. It is printed with its own heading, "Evaluation for Practical Courses (2 Credit Courses)", and immediately under it, "(other than Mini Project)".

munotes.in3

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

So none of section B's rules is a rule of this paper:

  • There is no certified journal requirement for appearing.
  • There is no rule that 80 per cent of the practicals must be completed, because this paper has no list of practicals.
  • There is no two-hour practical examination with a question on each module.
  • There is no mid-term practical examination.

Students who have heard these rules from seniors doing the practical papers sometimes carry them over. Do not. The documents that matter here are the ones this book walks you through: the proposal, the SRS, the UML set, the architecture document, the report and the user manual.

What MU does not print, and who decides it

MU's scheme is short. Several things a student wants to know are simply not in it. When something is not printed, it is decided by your college and your guide, and you should ask rather than assume.

QuestionWhat MU printsWho decides
How many students may work in one group?Nothing for this paperyour college
Must the project be new, or may it continue a seniors' project?Nothingyour college and guide
What format and length must the report be?Only that it is evaluated, for 5 marksyour college
Must the report be printed and bound?Nothingyour college
On which dates are the internal components assessed?Nothingyour college and guide
Which technology must be used?Nothing, except that GitHub is mandatoryyou, with your guide's agreement
Is Mini Project II a continuation of Mini Project I?They are separate papers with separate syllabiyour college and guide

One thing MU does print firmly is in Module 2: "Version control using GitHub (Mandatory)". That word is not in brackets for decoration. Whatever else your college allows, your code must be in a GitHub repository. Chapters 61 and 62 teach it, and Chapter 39 makes the first commit.

In the first week, take this list of questions to your guide and write down the answers. Every answer is a rule you will be held to, and a guess is a rule you might break.

Each of the eight components, and what earns it

MU prints the name of each component and its marks, and nothing about what earns them. The column "what you show" below is our reading of each name, based on what that name can sensibly mean for a project; your guide's and examiner's own expectations, if they give you any, come first.

Problem Identification & Project Proposal (5, guide). You show a real problem, evidence that it is real, and a proposal that says what you will build, for whom, why it is worth building and whether it can be built. Chapters 3 to 8 and 33.

munotes.in4

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

System Design (SRS, UML Diagrams, Architecture) (5, guide). MU names the three documents inside the brackets, so those are what is judged: the software requirements specification, the complete set of UML diagrams, and the architecture design document. Chapters 9 to 36.

Development Progress & Code Review (5, guide). Progress is judged over time, so show it over time: commits spread across the weeks, not all on the last night; issues opened and closed; a running build at each meeting. A code review means your guide reads your code and asks you about it, so be ready to explain any line you wrote. Chapter 49.

Internal Presentation / Review (5, guide). A presentation of the project to your guide, or to a panel your college forms, before the external examination. Treat it as the dress rehearsal for the external day. Chapter 74.

Working Application Demonstration (10, examiner). The application must run in front of the examiner and do what your SRS says it does, including refusing bad input and recovering from errors. Chapters 71 and 74.

Technical Design & Implementation (10, examiner). The quality of the design and of the code that implements it, and how faithfully the one follows the other. An examiner can open your repository, your diagrams and your code side by side. Chapters 19 to 31 and 38 to 49.

Project Report Evaluation (5, examiner). The final report, judged as a document: complete, correct, organised and readable. Chapters 66 and 73.

Viva Voce (5, examiner). Questions put to you in person about your project and the ideas behind it. "Viva voce" is Latin for "with the living voice", meaning an oral examination. Chapter 76 collects the questions examiners ask, with answers.

Why a copied project fails

Every year some students download a finished project from a website and submit it. This paper is built, perhaps by design, so that this fails at three separate points.

The code review. Your guide reads your code during the semester and asks you about it. A student who did not write it cannot explain why a function is written the way it is, or what happens if a line is removed.

The GitHub history. Git records who made every change and when. A repository with one enormous commit on the last day, or with commits by strangers, tells its own story before anyone asks a question.

The demonstration and the viva. An examiner who suspects a project was not built by the student asks for a small change, made live: add a field, change a validation rule, show where a particular request is handled. Only the person who built it can do that in five minutes.

munotes.in5

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

The worked project in this book is complete, and you may read every line of it. It is an example of the method, not a project to submit. Chapter 2 explains how to use it.

How this book follows the marks

ComponentWhere the book prepares you
Problem Identification & Project ProposalChapters 3 to 8, and the proposal in Chapter 33
System Design (SRS, UML Diagrams, Architecture)Chapters 9 to 31, and the documents in Chapters 34 to 36
Development Progress & Code ReviewChapters 38 to 49, with the review itself in Chapter 49
Internal Presentation / ReviewChapter 37 for the design review, Chapter 74 for the presentation
Working Application DemonstrationChapters 38 to 65, then Chapters 71 and 74
Technical Design & Implementationthe whole of Modules 1 and 2
Project Report EvaluationChapters 66 to 69 and 73
Viva Voceevery chapter's questions, then Chapters 75 and 76

What it does not mean

"Practical" does not mean a practical examination. The type is Practical because the credits are practical hours. There is no timed practical paper for this course.

The internal marks are not given for attendance. Each of the four is named after something you produce or do. A student who attends every meeting and brings nothing earns nothing under those names.

A good total does not guarantee a pass. Individual passing means 8 of 20 and 12 of 30, separately.

"No text book" does not mean "no preparation". The external examiner judges design and implementation, and asks questions in the viva. Both require you to understand the ideas this book teaches, not only to have built something.

Quick revision

  • No written examination. Internal 20 by the Project Guide, external 30 by the External Examiner.
  • Internal: Problem Identification & Project Proposal 5; System Design (SRS, UML Diagrams, Architecture) 5; Development Progress & Code Review 5; Internal Presentation / Review 5.
  • External: Working Application Demonstration 10; Technical Design & Implementation 10; Project Report Evaluation 5; Viva Voce 5.
  • Individual passing, 40 per cent in each: at least 8 of 20 and at least 12 of 30.
  • Section B's certified journal and 80 per cent rule are printed "(other than Mini Project)": they do not apply.
  • MU does not print group size, report format or dates: ask your guide in week one.
  • GitHub is printed as Mandatory.

Questions you must be able to answer

1. How is Mini Project I assessed? By two assessors and no written paper. The project guide awards 20 internal marks for the problem and proposal, the system design, the development progress and code review, and an internal presentation, 5 marks each. An external examiner awards 30 marks for the working application demonstration (10), the technical design and implementation (10), the project report (5) and the viva voce (5).

munotes.in6

How Mini Project I Is Marked: the Guide's Twenty, the Examiner's Thirty and Passing Each

2. A student has 19 internal and 11 external marks. Have they passed? No. MU's scheme requires individual passing at 40 per cent in each component, which is 12 of 30 in the external. Eleven is one short, so the student has not passed the paper, although the total of 30 is 60 per cent.

3. Does a student need a certified journal to appear for the Mini Project examination? No. The certified journal rule is in section B of the evaluation scheme, which is printed as applying to practical courses "other than Mini Project".

4. What does "Development Progress & Code Review" require you to show? MU prints only the name and the 5 marks. Read plainly, it asks for progress that can be seen over time, such as commits spread across the weeks and issues closed, and for code the guide can read and the student can explain line by line when asked.

5. What percentage and grade does 38 out of 50 give? 38 divided by 50 is 0.76, which is 76 per cent, and that falls in 70.0 to below 80.0, which is grade A (Very Good) with 8 grade points.

6. Your college has not said how many students may be in a group. What do you do? Ask the guide. MU's scheme for this paper does not print a group size, so the college's rule is the one that applies, and it should be confirmed in writing in the first week, along with the report format and the assessment dates.

Contents This chapter on its own page

munotes.in7

Chapter Two

The Design Phase at a Glance, and the Worked Project in This Book

Syllabus topic Module 1 (30 hours), "Problem Identification, Requirement Engineering & System Design Phase", as a whole: its five topic rows and the "Documentation Deliverables at the End of Module 1".

In one line

Module 1 is the design phase: in its 30 hours you find a real problem, show that it is worth solving and can be solved, write down exactly what the software must do, plan the work, draw the system in UML, design its architecture, and hand in four documents that let anyone build it.

In the wording to use when asked: Module 1 of Mini Project I covers problem identification and feasibility study, requirement engineering, SDLC planning, system modelling in UML and system architecture design, and ends in four deliverables: the project proposal, the SRS document, the complete UML set and the architecture design document.

Why a project begins with no code at all

Students who can program want to start programming. It is the part that feels like progress. MU puts thirty hours of design first, as half of the paper, for a reason every working software team learns the hard way: most failed software was built correctly to the wrong specification. It did what its builders thought was wanted, and what was wanted was something else.

Changing a sentence in a requirements document costs a minute. Changing the same decision after the database, the screens and the tests have all been built on it costs days. The design phase exists to make the expensive mistakes while they are still cheap, on paper, where a guide or a canteen owner can read them and say "no, that is not how it works here".

It also protects your marks directly. Two of the guide's four internal components, Problem Identification & Project Proposal and System Design (SRS, UML Diagrams, Architecture), are judged on Module 1's documents alone, before a line of code exists.

What MU prints for Module 1

MU heads the module "Problem Identification, Requirement Engineering & System Design Phase". Those three names are the three stages of the module. Under the heading come five topic rows and a list of four deliverables:

MU's topic rowWhat it asks you to doChapters
Problem Identification & Feasibility Studyfind a real problem, justify it, fix its scope, identify who it affects, and test whether it can be solved3 to 8
Requirement Engineeringwrite down what the system must do, how well, for whom, in what order, and within what limits9 to 14
Software Development Life Cycle (SDLC) Planningchoose how you will work, break the work down, schedule it, and plan the people and things it needs15 to 18
System Modeling using UMLdraw the system six ways: use case, class, sequence, activity, ER and deployment diagrams19 to 25
System Architecture Designdecide the frontend, the backend, the database schema, the API and the security26 to 31
munotes.in8

The Design Phase at a Glance, and the Worked Project in This Book

And the deliverables, printed as "Documentation Deliverables at the End of Module 1":

DeliverableWhat it isChapter
Project Proposalthe case for the project: problem, solution, feasibility, plan33
SRS Documentthe software requirements specification: exactly what the system must do34
Complete UML Setthe six diagrams, drawn consistently with each other35
Architecture Design Documenthow the system is built: its parts and the decisions behind them36

How the five rows become four documents

The rows are not five separate jobs. Each one feeds a document, and the documents build on each other in a fixed order, because each needs the one before it.

  1. The problem and its feasibility become the heart of the project proposal. You cannot propose a solution to a problem you have not established.
  2. The requirements become the SRS. You cannot specify requirements for a problem that has not been scoped.
  3. The SDLC plan goes into the proposal, as its plan of work, and stays with you as the project plan you track for the rest of the semester.
  4. The UML diagrams turn the SRS into pictures of the system: who uses it, what it holds, how it behaves. They make up the complete UML set, and the use case diagram also appears in the SRS.
  5. The architecture decides how the specified system will actually be built, and becomes the architecture design document.

Put as a chain: problem, then proposal, then SRS, then UML, then architecture. A guide who reads them in that order should find that each document answers the questions the one before it raised.

The thirty hours

MU allots 30 hours to Module 1. That is 30 hours for each student, because every student earns the 2 credits. A team of four has 120 hours for the design phase between them, and another 120 for the build.

Thirty hours sounds like a lot until it is spread over a semester of other papers. The worked team's plan, which Chapter 17 computes, finishes the four documents in the seventh week and holds its design review on the last day of it. Thirty hours in seven weeks is a little over four hours a week for each member, every week, and Chapter 18 shows that some weeks need more.

Two warnings from how students actually spend this time:

  • The first two weeks decide the project. A problem chosen in a hurry in week one is defended for the rest of the semester. Chapter 3 is about choosing well.
  • Documents take longer than they look. An SRS is not typed in an evening. It needs interviews first, and then several drafts, each read by someone else.
munotes.in9

The Design Phase at a Glance, and the Worked Project in This Book

The worked project in this book

Every chapter of this book teaches an idea and then does it, on one complete project, so that you see the finished article and not only a description of it. The project runs from the first chapter on problems to the last question of the viva. Here it is.

The problem. At a degree college, the canteen serves lunch through one counter during a 40-minute break, from 12:30 to 13:10. Students queue to order and pay, then wait again while the food is prepared. Some give up and leave the queue without eating. Others eat and arrive late for the 13:10 lecture.

The team. Four TY B.Sc. (Computer Science) students: Aditi Kulkarni, who leads the team and owns the requirements and the documents; Farhan Shaikh, who owns the backend and the database; Sneha Nair, who owns the pages and how they look; and Rohan D'Souza, who owns testing, deployment and the Android build. Their guide is Prof. S. Iyer. The canteen is run on contract by its owner, Lata Pawar, and its counter by Ganesh More.

The solution they arrive at. An application called Canteen Pre-order. Students order lunch from their phones before the break and choose a ten-minute pickup slot. The counter sees the orders slot by slot and moves each one through placed, being prepared, ready and collected. Students pay at the counter when they collect. The owner manages the menu, sets each day's stock, and reads the day's sales.

How it is built. With Node.js and the Express framework on the server, a MySQL database, and plain HTML, CSS and JavaScript in the browser, which is the stack MU's own earlier papers taught: Web Technologies, MEAN Stack Development and the database papers. An Android app wraps the same pages for students who prefer an app. Chapter 26 explains why each choice was made, and what would change for a project built in PHP, Django, the MERN stack or native Android.

How much of it the book shows. All of it. Every document is written out in Module 1's chapters. Every file of the application, every test, every configuration file, is printed in full in Module 2's chapters, and every terminal session printed in this book was run on a real Ubuntu machine against that exact code, with its output copied from the screen.

The story is fictional. The people, the college and the canteen are invented. The numbers about the canteen, such as how many students left the queue each day, are the worked team's observations within the story, and the book never presents them as facts about any real college.

munotes.in10

The Design Phase at a Glance, and the Worked Project in This Book

The one rule for using the worked project

Copy the method, never the project.

The worked project exists so that every idea in this book has a concrete, finished example. It is not a project for you to submit, and submitting it would fail in the ways Chapter 1 describes: at the code review, in the GitHub history, and at the demonstration, where an examiner can ask for a change made live.

Use it this way instead:

  1. Read the idea in each chapter.
  2. Read what the team did with it, and notice the decisions they made and why.
  3. Do the same step for your own problem, using the task list at the end of the chapter.
  4. Compare your version with theirs, and ask whether yours answers the same questions.

Your problem will be different from a canteen queue, and so will your requirements, your diagrams and your code. The questions each step asks will be exactly the same.

Working as a team

MU does not print a group size for this paper; your college decides it (Chapter 1). Most projects are done in small groups, and a group needs two things settled in the first week.

Who owns what. Ownership means one person is answerable for a piece of work being done and done well, not that they do it alone. The worked team split ownership by area, as above, and every member still wrote code and every member took part in every document review. Chapter 18 writes this down properly as a responsibility matrix.

Everyone must be able to explain everything. An examiner may ask any member about any part of the project. "Farhan did the database" is not an answer in a viva. Plan time for each member to walk the others through their part.

Do this for your project

  1. Settle your group with your guide, and get the answers to Chapter 1's list of questions: group size, report format, assessment dates.
  2. Agree who owns which area. Write it down.
  3. Create a shared folder for your documents, and name every file with the document and its version, for example srs-v0.3.docx. Chapter 32 sets out the conventions.
  4. Block about four hours a week, per person, for the next seven weeks, and expect some weeks to need more. Put them in your calendars now.
  5. Read Chapter 3 before you choose a problem.

What it does not mean

The design phase is not "documentation for the sake of marks". Each document answers questions the builders would otherwise answer by guessing, one at a time, in code.

Module 1 is not finished when the documents are submitted. Requirements change as you build. The documents are updated when they do, and the version history in each shows it.

munotes.in11

The Design Phase at a Glance, and the Worked Project in This Book

Following the worked project's stack is not required. It is one reasonable choice, explained in Chapter 26. Choose yours by the same reasoning.

Quick revision

  • Module 1 is the design phase: MU's heading is "Problem Identification, Requirement Engineering & System Design Phase".
  • Five topic rows: problem identification and feasibility; requirement engineering; SDLC planning; UML modelling; architecture design.
  • Four deliverables: project proposal, SRS document, complete UML set, architecture design document.
  • The documents build in order: problem, proposal, SRS, UML, architecture.
  • 30 hours per student; a team of four has 120 hours for the design phase.
  • The worked project, Canteen Pre-order, is fictional, complete, and printed in full. Copy the method, never the project.

Questions you must be able to answer

1. Why does Mini Project I begin with a design phase instead of with programming? Because a mistake in a document costs minutes to fix and the same mistake built into code, database and tests costs days. The design phase makes the expensive decisions while they are cheap, and gets them checked by people who know the problem, before anything is built on them.

2. Name the five topics of Module 1 and the four documents it ends in. Problem identification and feasibility study; requirement engineering; SDLC planning; system modelling using UML; system architecture design. The documents are the project proposal, the SRS document, the complete UML set and the architecture design document.

3. In what order are the Module 1 documents written, and why that order? Proposal, then SRS, then the UML set, then the architecture document, because each needs the one before it: requirements cannot be specified for an unscoped problem, the diagrams draw the specified requirements, and the architecture decides how the specified and modelled system is built.

4. Which internal marks depend on Module 1 alone? Problem Identification & Project Proposal, and System Design (SRS, UML Diagrams, Architecture): 10 of the guide's 20 marks.

5. Your group copies its design from a finished project online. What goes wrong? The design does not match the group's own problem, so the guide's questions about the problem and its stakeholders cannot be answered; and later the code review, the Git history and the live demonstration all expose work the group did not do.

Contents This chapter on its own page

munotes.in12

Chapter Three

Finding a Real-World Problem: Industry, Social and Institutional

Syllabus topic Module 1, "Problem Identification & Feasibility Study: Identification of a real-world problem (industry / social / institutional)".

In one line

A real-world problem is a difficulty that real people have today, in a place you can go to, which you can see and measure, and which software could reduce; you find one by watching and asking, not by browsing lists of project titles.

In the wording to use when asked: problem identification is the first activity of a software project, in which a genuine need is discovered in an industry, social or institutional setting, observed and described in terms of its effects on the people concerned, before any solution is proposed.

Why this step decides the semester

Everything you do for the next fifteen weeks rests on the problem you choose now. Your requirements come from the people who have it. Your tests check that it is solved. Your demonstration shows it being solved. Your viva opens, very often, with some form of "What problem does your project solve, and how do you know it is a real one?"

A student who chose "Hospital Management System" from a list of titles has no hospital, no patients and no staff to interview. Every requirement is invented, every test checks an invention, and the first question of the viva has no honest answer. A student who chose a problem they can walk up to and watch has an answer to every one of those questions, because the answer is out there to be looked at.

MU's own description of the paper says students "design solutions for real-world industry, social, or institutional problems". The word real is the whole brief.

What makes a problem real

A problem is real when all four of these are true:

  1. Somebody has it now. Not "people might one day want". A person you can name, or a group you can point to, is having this difficulty this week.
  2. It happens somewhere you can go. You can stand in the place, watch the problem happen, and talk to the people it happens to.
  3. It can be seen and counted. Minutes lost, errors made, money spent, trips wasted, forms lost. If it cannot be measured at all, you will never be able to show that your software reduced it.
  4. Software can reduce it. Some problems need a new building, more staff or a change of rule, and no program will help. Yours must be one where recording, calculating, reminding, sharing or checking information makes a difference.

The three kinds MU names

MU's label names three settings in which to look: "industry / social / institutional". They are not rigid boxes, and a good problem can sit on a boundary, but each points you at different people and different kinds of difficulty.

Industry

An industry problem belongs to a business: a shop, a clinic, a workshop, a transport operator, a tuition class. Its costs are usually money and time, and its owner will often tell you exactly what the problem costs them, because they feel it in their takings.

munotes.in13

Finding a Real-World Problem: Industry, Social and Institutional

Examples of the kind:

  • A tuition class tracks fees in a notebook and cannot tell which students owe money until the month's end.
  • A small clinic books appointments by phone, patients arrive together, and the waiting room overflows while the afternoon is empty.
  • A print shop takes orders on WhatsApp, loses files in the chat, and prints the wrong version.
  • A two-wheeler service centre writes job cards by hand, and customers phone repeatedly to ask whether the bike is ready.

Social

A social problem belongs to a community rather than to a business: a housing society, a neighbourhood, a group of volunteers, a public service. Nobody profits directly from solving it, which makes the people who have it glad of help and slow to fund it.

Examples of the kind:

  • A housing society's water tanker bookings and maintenance complaints are managed through one overloaded WhatsApp group.
  • A blood donors' group keeps donors in a spreadsheet and cannot quickly find who is eligible to donate again.
  • A volunteer group teaching children on weekends schedules volunteers by phone and ends up with too many one week and none the next.
  • A local library for senior citizens lends books through a register and cannot tell which books are overdue.

Institutional

An institutional problem belongs to an organisation such as a college, a school, a hospital's administration or a government office. For a student this is the most reachable kind of all, because you are inside an institution already: your own college.

Examples of the kind:

  • Students queue at the college canteen for most of the lunch break.
  • Computer lab slots for projects are booked on a paper sheet pinned to a door.
  • Library reading-room seats are taken early and held all day with a bag.
  • Lost items handed to the office sit in a drawer, and their owners never find out.
  • Placement drives are announced on notice boards, and students miss the registration deadlines.

Where problems hide

Three signs point at a problem worth investigating, and you can look for them anywhere.

A register. Wherever people write information into a book by hand, somebody later has to search it, total it or copy it. Registers are slow to search and cannot remind anyone of anything.

A queue. Wherever people wait in line, time is being lost, usually because a request and its fulfilment happen at the same place at the same moment. Separating the two is often exactly what software does well.

munotes.in14

Finding a Real-World Problem: Industry, Social and Institutional

A group chat carrying work. Wherever a WhatsApp group is being used as a booking system, a complaints desk or a task list, information is scrolling out of sight and nobody can see the current state of anything.

To find these signs, use your eyes and your questions:

  • Observe. Go and watch a place at its busiest time for half an hour. Note what people wait for, what they write down, what they ask for twice.
  • Ask one question. "What part of your day takes longer than it should?" asked of a shopkeeper, a clerk or a lab assistant will produce more real problems than an hour of browsing.
  • Shadow. Follow one person through one task, from start to finish, and write down every step.
  • Look at the paperwork. The forms, slips and registers a place uses tell you what information matters to it, and where it is copied by hand.

Is it the right size and shape for this paper?

Not every real problem suits a 60-hour project. Test each candidate against these questions before you commit to it.

QuestionWhy it mattersA warning sign
Can you reach the people who have it, more than once?You must interview them for requirements and show them the result for acceptance"We will imagine what users want"
Can the team build a useful first version in its hours?MU allots 60 hours per student, half of them to designa problem that needs ten kinds of user and fifty screens
Does it need a frontend, a backend and a database?Module 2 assesses all three, with authentication and validationa problem solved by a static page of information
Can you get the data it needs, legally and without harm?Real data about people is protected, and you are studentsmedical records, identity numbers, bank details
Can it run without things you cannot get?Hardware, government access and payment accounts are hard for students to obtainbiometric devices, government databases, a merchant account
Is there a person who will say whether it worked?Somebody must be able to accept or reject the resultnobody in particular is waiting for it

A problem that fails one of these can sometimes be reshaped. A clinic problem that needs medical records can become an appointment problem that needs only names, times and phone numbers. A payment problem can become an ordering problem where payment stays at the counter. Reshaping a problem to fit the paper is good judgement, provided you say so honestly in your scope (Chapter 4).

A problem is not a solution

The commonest mistake at this stage is to start with an answer.

"We want to build an app for the canteen" is a solution. It says nothing about what is wrong. "Students spend most of their 40-minute lunch break queuing at the canteen counter" is a problem. It says who suffers, what the difficulty is, and it can be checked by going to look.

munotes.in15

Finding a Real-World Problem: Industry, Social and Institutional

Keep the two apart for as long as you can, for two reasons. The first is that the problem, once stated properly, may suggest a better solution than the one you started with. The second is that your examiner will judge the solution against the problem, and a problem that was really a solution in disguise gives them nothing to judge it against.

Written as a solutionRewritten as the problem
An online library booking systemReading-room seats are held all day with bags, so students who arrive after 9:00 cannot find a seat
A WhatsApp bot for the housing societyTanker bookings and complaints are lost in a group chat of 200 messages a day, and the secretary cannot see what is pending
A fee management system for the tuition classThe owner cannot tell which students owe fees until the month ends, and loses money chasing old dues

What to leave alone

Some choices look attractive and go badly, and it is kinder to say so now.

A clone of a famous product. "An Amazon clone" or "a food delivery app like Swiggy" has no users you can reach and no problem you can observe. It also invites comparison with a product built by thousands of engineers.

A problem only you have. A project whose only user is its builder cannot be accepted by anyone else.

Anything that needs sensitive personal data. Health records, identity numbers and bank details carry legal duties you cannot meet as a student project, and your guide will rightly refuse them.

Anything that moves real money. Taking payments online needs a payment gateway account, and opening one needs business verification a student team does not have. Keep payment outside the system, as the worked project does.

A title from a list. Lists of project titles circulate every year. A title is not a problem, and the student who picks one has to invent every requirement.

Doing it properly: the ethics of looking

Watching people and asking them questions is research, and it carries simple duties.

  • Ask before you observe or photograph in a place that is not public: the canteen owner, the librarian, the lab in-charge. Say it is a college project.
  • Do not record who people are unless you must. Count people, time them, but do not write down names. A survey should not ask for names unless you need them.
  • Tell people what it is for and that they can decline.
  • Keep what you collect to what you need, and keep it private.
munotes.in16

Finding a Real-World Problem: Industry, Social and Institutional

These duties also make your evidence better. People who know what you are doing and trust you give you straighter answers.

The worked project: how the team found its problem

In their first week, the four members of the worked team each listed problems they had seen, and then scored the list together against the table above. Nine candidates came out of it:

CandidateKindReach the people?Sensitive data?Verdict
Canteen queue at lunchinstitutionalyes, every daynoshortlist
Computer lab slot bookinginstitutionalyesnoshortlist
Library reading-room seatsinstitutionalyesnoshortlist
Housing society complaintssocialone member's society onlynoreject: one group to test with
Tuition class feesindustrya relative's classfee recordsreject: money and minors' data
Clinic appointmentsindustryno clinic agreedhealth datareject
Lost and found at collegeinstitutionalyeslittlereject: too few cases to measure
Placement drive noticesinstitutionalyeslittlereject: the placement cell already has a portal
Blood donor findersocialno donors' group knownhealth datareject

They then spent an hour at each shortlisted place, at its busiest time. The lab bookings turned out to be a nuisance, not a problem: the sheet on the door worked, and the lab assistant was content with it. The librarian did not want the reading room's rules changed. The canteen at lunch was plainly a problem, it happened every day, and the owner, Lata Pawar, was willing to talk.

So they observed it properly. For five days, Monday 3 to Friday 7 August 2026, one of them stood near the counter from 12:30 to 13:10 and counted two things: the students served, and the students who joined the queue and left it without buying anything.

DayServedLeft the queue without buying
Monday21231
Tuesday19827
Wednesday22536
Thursday20729
Friday21833
Total1060156

That is 212 students served on an average day, and 31.2 walking away. They also timed 20 students a day, 100 in all, from the moment each joined the queue to the moment they had food in hand: the mean wait was 16 minutes, the median 15, the longest 27 minutes, which is two thirds of a 40-minute break.

They asked the owner's permission before they began, counted people without recording who they were, and did not photograph anyone.

At the end of the week they wrote the problem down in one paragraph, and deliberately did not mention software:

At our college canteen, students have a 40-minute lunch break and one counter serves them all. Students queue to order and pay, then wait again for the food. Over five days we counted 212 students served and 31 leaving the queue without buying on an average day, and timed waits of 16 minutes on average and up to 27 minutes. Students who stay are often late for the 13:10 lecture; students who leave go without lunch.

munotes.in17

Finding a Real-World Problem: Industry, Social and Institutional

That paragraph is the seed of their problem statement, which Chapter 4 develops and justifies.

Do this for your project

  1. Each member writes down five difficulties they have seen this month, in any of the three kinds. Aim for fifteen or more candidates as a group.
  2. Score every candidate against the size-and-shape table. Reject honestly.
  3. Shortlist three. Spend an hour at each, at its busiest time, and talk to the person in charge.
  4. Choose one. Ask permission from the person in charge before you study it further.
  5. Measure it for a few days: count, time, or tally whatever the difficulty costs.
  6. Write it down in one paragraph that describes the problem and its evidence, and does not mention software.

Mistakes that cost marks

Choosing from a list of titles. The guide's first questions about users and evidence have no answers, and the proposal mark suffers at once.

Describing a solution as the problem. "The college needs an app" cannot be justified, only asserted.

No evidence. "Students face a lot of problems" is an opinion. "31.2 students a day left the queue without buying" is evidence.

Too big. A problem needing a dozen user types and months of work leads to a half-built project, and a half-built project scores worse than a small complete one.

Nobody to accept it. Without a real person who will say whether it works, acceptance testing (Chapter 54) cannot be done.

Quick revision

  • A real-world problem: somebody has it now, somewhere you can go, it can be measured, and software can reduce it.
  • MU's three kinds: industry (a business), social (a community), institutional (an organisation such as your college).
  • Signs of a problem: a register, a queue, a group chat carrying work.
  • Find problems by observing, asking, shadowing and reading the paperwork, not from lists of titles.
  • Test for size and shape: reachable users, fits 60 hours, needs frontend, backend and database, data you may use, nothing unobtainable, someone to accept it.
  • A problem is not a solution. State the difficulty first, without naming software.
  • Ask permission, record no names you do not need, keep what you collect private.

Questions you must be able to answer

1. What is a real-world problem, in the sense this paper uses? A difficulty that real, identifiable people have now, in a setting the students can visit, which can be observed and measured, and which software can reduce by recording, calculating, sharing or checking information.

munotes.in18

Finding a Real-World Problem: Industry, Social and Institutional

2. Distinguish industry, social and institutional problems, with an example of each. An industry problem belongs to a business and is usually measured in money or time, such as a clinic's phone bookings. A social problem belongs to a community, such as a housing society's complaints lost in a group chat. An institutional problem belongs to an organisation such as a college, such as students losing their lunch break in the canteen queue.

3. Why is "we will build a canteen app" not a problem statement? Because it names a solution and says nothing about what is wrong, for whom, or how much. The problem is the difficulty itself, such as students losing most of their break in the queue, and a solution can only be judged against a problem stated that way.

4. How did the worked team know their problem was real? They observed it on five consecutive days and measured it: an average of 212 students served and 31.2 leaving the queue without buying each day, and waits averaging 16 minutes and reaching 27 minutes in a 40-minute break.

5. Why did the worked team reject the tuition-class fees problem? Because it involved money and the personal data of minors, and they could reach only one class to test with. A student project cannot take on those legal duties, and one class gives too little to test against.

6. What ethical duties apply when you observe a problem? Ask permission before observing or photographing in a place that is not public, explain that it is a college project, record no personal details you do not need, and keep what you collect private.

Contents This chapter on its own page

munotes.in19

Chapter Four

Problem Justification and Scope Definition

Syllabus topic Module 1, "Problem Identification & Feasibility Study: ... Problem justification and scope definition".

In one line

Justifying a problem means showing, with evidence, that it is big enough and costly enough to be worth solving and that software is a sensible way to solve it; defining its scope means writing down exactly what your project will deal with and, just as firmly, what it will not.

In the wording to use when asked: problem justification establishes the significance of the identified problem through evidence of its frequency, extent and impact, and of the inadequacy of existing alternatives; scope definition fixes the boundary of the proposed system by listing what is in scope, what is out of scope, and the objectives by which success will be judged.

Why both are needed before anything else

A problem you have found is still only a claim. Your guide, and later the examiner, will ask two questions of it before they care about any design: is it worth solving? and what exactly are you going to solve?

Justification answers the first. Without it, your project is a solution looking for a reason. With it, every later decision has something to be measured against: a feature is worth building if it reduces the problem you have shown to exist.

Scope answers the second. Without it, a project grows every week, because every conversation suggests one more feature, and it ends the semester large, unfinished and untested. With a written scope, a new idea has to argue its way in against the time you have, and most of them wait for another release.

Both land in the project proposal (Chapter 33), which is marked by your guide under Problem Identification & Project Proposal.

Justifying the problem

A justification is an argument, and like every argument it is only as good as its evidence. Five things make one convincing.

1. How many people it affects. Count them, or estimate them from counts. "Students" is vague; "about 212 students on an ordinary day" is not.

2. How often it happens. Once a year is a nuisance. Every working day is a problem.

3. What it costs them. In time, money, missed opportunities, stress or error. Put a number on it wherever you can.

4. What happens if nothing is done. Problems rarely stay still. A queue that grows with admissions, a register that grows every year, a chat group that grows every month.

5. Why the existing ways of coping are not enough. Somebody is already coping with the problem somehow. Say what they do, and why it falls short.

Kinds of evidence, and their weight

EvidenceWhat it looks likeIts weight
Your own counts and measurements"31.2 students a day left the queue"strongest: you saw it, and you can say how you counted
Records the place already keepssales slips, registers, complaint logsstrong, if you can see them and they are complete
An interview with the person in charge"we throw away about Rs 600 of food a day"good, but it is one person's estimate, and should be labelled so
A survey of the people affected"131 of 180 said they were late at least once a week"good for opinions and habits, weaker for facts, and only as good as its sample
General statements"students face many problems"none
munotes.in20

Problem Justification and Scope Definition

Surveys: what they can and cannot show

A survey is the most popular kind of evidence in student projects and the most often misused. Keep three things in mind.

Report the numbers, not only the percentages. "72.8 per cent" hides whether you asked 11 people or 1,100. Write "131 of 180 (72.8 per cent)".

Say how the people were chosen. If you shared a form in your class groups, those who replied are the ones who saw it and cared to answer. That is called a convenience sample, and it is not a random sample of all students. It is still useful, but its results describe the people who replied, and your report should say so.

Ask about behaviour, not about your solution. "Were you late for a lecture because of the queue last week?" produces evidence. "Would you use a great app to order food?" produces politeness. People say yes to hypothetical products they will never use.

Consider the alternatives honestly

Software is not the answer to every problem, and a justification that pretends otherwise is weak. List the other ways the problem could be tackled, and say why each is or is not better.

Examiners respect a team that considered cheaper alternatives and can explain why they chose software. They distrust one that never thought of any.

The problem statement

The justification is summarised in a problem statement: a short paragraph that says who has the problem, what it is, where and when it happens, how big it is, and what it causes. It does not describe the solution.

A dependable shape for it:

  1. Who is affected, and how many.
  2. What the difficulty is, in their terms.
  3. Where and when it happens.
  4. How much, with your evidence.
  5. So what: the consequence for them, and for anyone else.

Keep it to a paragraph. If it needs a page, the problem is not yet understood.

Defining the scope

Scope is the boundary of your project. Inside it is everything the project promises to deliver. Outside it is everything it does not, however related or desirable.

A scope statement has four parts.

The objectives. What the project must achieve, stated so that someone can later check whether it did. The usual test for a well-made objective is that it is specific, measurable, achievable, relevant to the problem, and tied to a time. For a student project the time is the semester.

munotes.in21

Problem Justification and Scope Definition

In scope. The capabilities the system will have, at the level of a list, not yet as detailed requirements. The detail comes in the SRS.

Out of scope. The capabilities it will deliberately not have, each with a one-line reason. This list is not an admission of weakness. It is the most useful list in the document, because it tells everyone, including your future selves, what not to spend time on.

The first release. If the in-scope list is still too long for your hours, mark which parts make the first release, sometimes called the minimum viable product: the smallest version that solves enough of the problem to be worth using. Chapter 13 does this properly with priorities.

Scope creep, and how a written scope stops it

Scope creep is the slow, unplanned growth of a project's scope, one reasonable-sounding addition at a time. "Could it also send an SMS?" "Could students rate the food?" "Could the owner see last month's sales too?" Each is small. Together they eat the testing and documentation weeks, and the project ends half-built.

A written scope turns each such idea into a decision instead of a drift. When someone suggests an addition, you ask three questions: does it serve the objectives, can we afford the hours, and what will we drop to make room? Most ideas are then written into the out-of-scope list as "a later release", which is honest and costs nothing.

The worked project: justification

After the week of observation in Chapter 3, the worked team gathered three more kinds of evidence.

A survey. A short form, shared in the class groups of several years, with no names asked. 180 students replied in the week of 3 August 2026:

QuestionYesOut ofPer cent
Have a smartphone with internet17618097.8
Late for the 13:10 lecture because of the queue, at least once a week13118072.8
Skip lunch at least twice a week because of the queue4918027.2
Would order from the phone before the break14218078.9
Prefer to pay at the counter (cash or UPI)12418068.9
Prefer to pay online in the app5618031.1

The report says plainly that this is a convenience sample of students in those groups. The first question matters more than it looks: if few students had phones with internet, a phone-based solution would fail, and 176 of 180 settles it.

munotes.in22

Problem Justification and Scope Definition

An interview with the owner, Lata Pawar, on 10 August 2026. She put the average lunch bill at Rs 68 and estimated that about Rs 600 of food is thrown away on an ordinary day, because the cooks guess each morning how much of each dish to make. The team recorded both figures as her estimates.

The alternatives, as they wrote them down:

AlternativeWhy it does not solve the problem
Do nothingThe observed losses continue: 31.2 students a day go without lunch or leave the queue
Open a second counterNeeds another member of staff for the break every day, which the owner cannot pay for
Paper tokens given out at the counterStudents still queue to order and pay; only the wait for food is organised
A shared online form for ordersNo stock limit, so dishes are over-ordered; no status, so students still crowd the counter; no list for the kitchen
A general food ordering platformBuilt for delivery from restaurants, not for collection from a college counter within a fixed break

The problem statement they submitted:

Students at our college have a 40-minute lunch break, from 12:30 to 13:10, and one canteen counter where they must both order and wait for their food. Over five days of observation we counted an average of 212 students served and 31.2 leaving the queue without buying each day, and timed waits averaging 16 minutes and reaching 27 minutes. In our survey, 131 of 180 students said the queue makes them late for the 13:10 lecture at least once a week, and 49 of 180 skip lunch at least twice a week because of it. The canteen loses sales it could have made, and, by the owner's estimate, throws away about Rs 600 of food a day because the kitchen cannot know in advance what will be ordered.

Notice what the statement does. It names who, what, where and when; it gives numbers and says where they came from; it states the consequences for students, for teachers and for the canteen; and it does not mention an app.

The worked project: scope

Objectives. By the end of the semester, the project will deliver a system that:

  • O-1 lets a student order lunch before the break and collect it within five minutes of a chosen pickup time;
  • O-2 is expected to halve the number of students who leave the queue without buying, from 31.2 a day, when the canteen uses it;
  • O-3 tells the kitchen, by 12:20 each day, how many of each dish to prepare for each pickup slot;
  • O-4 shows the owner the day's sales without counting slips.

In scope:

  • student accounts, created by the students themselves with their college email address;
  • today's menu, with prices, a vegetarian mark and what is still available;
  • ordering for one of four ten-minute pickup slots, with a stock limit on every dish;
  • a number for each order, and its status visible to the student;
  • cancelling an order that has not started being prepared;
  • a counter screen of the day's orders by slot, on which staff move each order from placed to collected;
  • a kitchen list of what is still to be made for each slot;
  • the owner's pages for the menu, the day's stock, the daily report and staff accounts;
  • a web application for phones and laptops, and an Android app.
munotes.in23

Problem Justification and Scope Definition

Out of scope, with reasons:

Not in this projectWhy
Online paymentneeds a payment gateway merchant account and business verification, which the team cannot obtain; students pay at the counter as they do now, which 124 of 180 prefer anyway
Delivery to classroomsthe canteen has no staff to deliver
SMS alertseach message costs money and needs a paid provider account
A penalty for students who order and do not collecta rule the college would have to agree first; noted for a later release
More than one canteenthe college has one
Stock of raw materialsa different problem, the kitchen's, not the queue's

Every item in the out-of-scope table came up in a conversation during the first weeks. Writing it down with its reason ended the discussion of each, and when the owner asked again in week eight about online payment, the team pointed to the table and to the proposal she had signed.

Do this for your project

  1. Gather at least two kinds of evidence beyond your own observation: records, an interview, a survey.
  2. If you run a survey, keep it short, ask about behaviour, ask no names, and report numbers with their totals and how people were reached.
  3. List the alternatives to software, and one honest line on each.
  4. Write your problem statement in one paragraph: who, what, where and when, how much, so what. No mention of software.
  5. Write three to five objectives that someone could check at the end of the semester.
  6. Write the in-scope list and the out-of-scope list, each item of the second with its reason. Show both to the person in charge of the place, and to your guide.

Mistakes that cost marks

Justifying with adjectives. "Huge", "serious", "many" persuade nobody. Numbers with sources do.

Percentages without totals. "80 per cent of students agree" invites the question "80 per cent of how many?".

A survey about the solution. "Would you like an app?" measures politeness, not need.

No alternatives considered. It suggests the solution was chosen before the problem was understood.

munotes.in24

Problem Justification and Scope Definition

An empty out-of-scope list. It means the scope has no boundary, and nobody believes a student project will build everything.

Objectives nobody can check. "To make the canteen efficient" cannot be tested; "collect within five minutes of the pickup time" can.

Quick revision

  • Justification: evidence of how many, how often, how costly, what if nothing is done, and why current ways fall short.
  • Evidence ranks: your own measurements, then records, then interviews, then surveys; general statements count for nothing.
  • Report survey results as counts with totals and say how people were reached; a convenience sample describes those who replied.
  • The problem statement: who, what, where and when, how much, so what, and no solution.
  • Scope: objectives, in scope, out of scope with reasons, and the first release.
  • Scope creep is unplanned growth; a written scope turns each addition into a decision.

Questions you must be able to answer

1. What is the difference between identifying a problem and justifying it? Identifying it establishes that a difficulty exists and describes it. Justifying it shows with evidence that it is significant enough to be worth solving, by how many people it affects, how often, at what cost, and why the existing ways of coping are inadequate.

2. What makes survey evidence weak, and how do you report it honestly? It is weak when the sample is small or self-selected, when it asks about a hypothetical solution rather than actual behaviour, or when only percentages are reported. Report the counts with their totals, say how respondents were reached, and ask about what people actually do.

3. What is scope creep, and how does a written scope prevent it? Scope creep is the unplanned growth of a project through a series of small additions, which consumes the time needed for testing and documentation. A written scope, with an out-of-scope list and objectives, makes each proposed addition a decision to be justified against the objectives and the hours available.

4. Why did the worked team leave online payment out of scope? Because accepting payments online requires a payment gateway merchant account and business verification that a student team cannot obtain, and because paying at the counter is what students already do and what 124 of the 180 surveyed preferred.

5. Write an objective for the worked project that can be checked, and one that cannot. Checkable: a student who pre-orders collects the food within five minutes of the chosen pickup time. Not checkable: the canteen becomes more efficient, because nothing says how efficiency would be measured or what counts as success.

Contents This chapter on its own page

munotes.in25

Chapter Five

Stakeholder Identification

Syllabus topic Module 1, "Problem Identification & Feasibility Study: ... Stakeholder identification".

In one line

A stakeholder is anyone who will use the system, run it, pay for it, approve it, maintain it or be affected by it; identifying them early tells you whose needs the system must meet and whom you must talk to before you write a single requirement.

In the wording to use when asked: stakeholder identification is the systematic discovery of every person, group or organisation that has an interest in, influence over, or is affected by the proposed system, recorded in a stakeholder register together with each one's role, interest, influence and the way they will be involved in the project.

Why this comes before requirements

Requirements come from people. If a group of people is missing from your list, their needs are missing from your requirements, and you find out when they reject the finished system or when it fails in their hands.

The failure is not rare. The most common way a student project goes wrong at the demonstration is that it works perfectly for the people the team thought of, and not at all for someone the team never asked. A canteen system designed only around students forgets the person at the counter who has to use it with one hand while taking money with the other.

Stakeholder identification is also cheap. It costs an afternoon of thinking and a few conversations. Missing one costs weeks.

Who counts as a stakeholder

Cast the net wide at first. Anyone in any of these groups is a stakeholder:

KindWho they areThe question they answer
Primary userspeople who use the system directly to do their own task"what must it let me do?"
Secondary userspeople who use it occasionally, or use what it produces"what do I need from it now and then?"
Operators and administratorspeople who keep it running day to day: set it up, add data, fix accounts"what must I be able to change and repair?"
Owners and sponsorswhoever has the problem and decides whether the system is used"is it worth it, and does it do what I need?"
Approverswhoever must allow it: an institution's head, a department, the law"may this be done here, and on what terms?"
Maintainerswhoever keeps it going after the builders leave"can we look after it?"
Affected partiespeople who never touch it but whose day it changes"what does it change for me?"

Two of these are forgotten more often than all the rest together.

The maintainers. A student team graduates. If the system is to be used after the semester, somebody else must host it, back it up and restart it when it fails. Who is that person, and what will they need?

munotes.in26

Stakeholder Identification

The affected parties. People who never log in can still be helped or harmed. In the worked project, the teachers of the 13:10 lectures never touch the system, and they are exactly the people who notice when students stop arriving late.

System stakeholders and project stakeholders

There is a second list, and it should be kept separate from the first.

The system's stakeholders are the people above: those with an interest in the software once it exists.

The project's stakeholders are those with an interest in the project as a piece of work: your team, your project guide, and the external examiner. They care about the process, the documents and the marks, not about using the system.

Both lists matter, but they matter differently. Requirements come only from the system's stakeholders. Your guide is not a canteen user, and a requirement that exists only because "the guide wanted it" should be questioned. The project's stakeholders shape how you work: the deadlines, the documents and the standard of evidence.

How to find them

Five ways to find stakeholders you would otherwise miss:

Follow the process. Write down every step from the beginning of the task to its end, and ask who does each step. Every name is a stakeholder.

Follow the data. For each piece of information the system will hold, ask who provides it, who reads it, and whose information it is.

Follow the money. Who pays for anything the system needs? Who earns more or loses less because of it?

Follow the approvals. Whose permission is needed to install it, to use the college's machines, to put a notice on the board, to use student email addresses?

Ask every stakeholder, "Who else should we talk to?" Each conversation ends with this question, and it is the one that finds the people you did not know existed.

The stakeholder register

The findings go into a stakeholder register: a table, kept up to date for the life of the project, with one row per stakeholder.

ColumnWhat goes in it
Stakeholdera named person, or a group with a named contact
Kindfrom the table of kinds above
Interestwhat they want from the system, in their own terms
Influencehow much they can decide or block: high, medium or low
What they need from usinformation, a demonstration, a sign-off, training
How we involve theminterview, survey, review of a document, acceptance test

Name real people wherever you can. "Canteen staff" is a group; "Ganesh More, who runs the counter" is someone you can phone.

The power and interest grid

Stakeholders differ in how much they care and how much they can do about it, and they need different treatment. A common way to sort them is a grid of power, meaning their ability to decide or block, against interest, meaning how much the system affects them.

munotes.in27

Stakeholder Identification

Low interestHigh interest
High powerKeep satisfied: brief them at key points, ask for their approvalManage closely: involve them throughout, agree every major decision
Low powerMonitor: keep an eye on them, inform them if things changeKeep informed: consult them, show them drafts, test with them

The grid is a tool for deciding how to involve people, not a ranking of whether they matter. The low-power, high-interest group is where most of your users live, and they decide whether the system is actually used.

The worked project: the stakeholder register

The worked team built its first register in the second week, following the process of a canteen lunch from the kitchen to the counter.

StakeholderKindInterestInfluenceHow we involve them
Students, about 212 served a dayprimary userseat within the break, not be latelow each, high togethersurvey; interviews with six; acceptance test
Ganesh More, counter staffprimary user and operatora calmer counter, fewer arguments about the ordermediuminterview; watch him at work; test every counter screen with him
The kitchen: three cooks and a helpersecondary usersknow what to cook, and how much, before the rushlowinterview with the head cook; test the kitchen list
Lata Pawar, canteen ownerowner and administratormore sales, less waste, a view of each day's takingshighinterviews; approve the scope; manage the menu and stock
The principal's officeapproverorder and safety on campus; use of the college's email and machineshigha letter asking permission; a demonstration before use
The IT lab in-chargemaintainer and approvera server that is safe, simple and does not need him at lunchtimehigha meeting about hosting; the server configuration document
Teachers of the 13:10 lecturesaffected partiesfewer students arriving latelowinformed; asked once whether late arrivals fell

And, kept separate, the project's own stakeholders:

StakeholderInterest
Prof. S. Iyer, project guidea well-run project, documents on time, 20 internal marks honestly earned
The external examinera working application, sound design, a good report, clear answers
The teama project they can finish, explain and be proud of

Placed on the grid:

Low interestHigh interest
High powerthe principal's officeLata Pawar; the IT lab in-charge
Low powerthe teachersstudents; Ganesh More; the kitchen

The stakeholder they nearly missed

The team's first list had no kitchen on it. They had followed the process from the student's side: order, pay, wait, collect. The cooks were behind a wall and never met a student.

munotes.in28

Stakeholder Identification

It was the question "Who else should we talk to?", put to Ganesh at the end of his interview, that found them. "Ask the cooks," he said. "They are the ones who run out."

The head cook's interview changed the design. Every morning the kitchen guessed how much of each dish to make, and the guess was often wrong in both directions: popular dishes ran out by 12:45, and the rest were thrown away. What the kitchen needed was not an app for students at all. It was a list, before the rush, of how many of each dish had been ordered for each pickup slot. That became a requirement of its own, the kitchen list (FR-16 in Chapter 10), and it is also what makes the owner's hope of less waste, from Chapter 4, possible.

A team that never talked to the kitchen would have built a system that reduced the queue and did nothing for the waste, and would not have known what it had missed.

Knowing your users: user classes

For the primary and secondary users, it helps to write a short description of each user class: a group of users who use the system in the same way and need the same things. These descriptions go straight into the SRS (Chapter 34), under "user classes and characteristics".

User classHow manyWhat they knowHow they will use itWhat matters most to them
Studentabout 212 a dayuse phone apps daily; know the menuon a phone, often on mobile data, in a few minutes between lecturesspeed: order in a few taps, see the order number clearly
Counter staffone at a timelittle computer use; comfortable with a phoneon a tablet or laptop at the counter, during the rush, often while handling moneybig, clear buttons; nothing that needs typing during the break
Owneroneuses a phone for business dailymornings and evenings, not during the rushsetting stock quickly; the day's takings at a glance

Notice how much design follows from this table before any design has been done. The counter screen must work without typing. The student pages must work on a phone on mobile data. The owner's pages can be denser, because the owner uses them at a quiet time.

Do this for your project

  1. Walk through the process from start to end and list who does each step.
  2. Follow the data, the money and the approvals, and add everyone you find.
  3. Check the two forgotten groups: who will maintain it, and who is affected without using it.
  4. Build the stakeholder register, with a named contact for each row.
  5. Place each stakeholder on the power and interest grid, and write down how you will involve each one.
  6. Write a user class table for your primary and secondary users.
  7. End every interview with "Who else should we talk to?", and add whoever it names.
munotes.in29

Stakeholder Identification

Mistakes that cost marks

Listing only "users" and "admin". Real systems have owners, approvers, maintainers and affected parties, and a guide will ask about each.

Mixing up the two lists. The guide and the examiner are stakeholders of the project, not of the system, and requirements do not come from them.

Groups with no names. "Staff" cannot be interviewed. A named contact can.

A register that is never used. The register is the plan for your interviews and your acceptance tests. If no row leads to an interview, a review or a test, it was written for decoration.

Quick revision

  • A stakeholder is anyone who uses, runs, pays for, approves, maintains or is affected by the system.
  • Kinds: primary users, secondary users, operators, owners, approvers, maintainers, affected parties.
  • Keep the system's stakeholders separate from the project's (team, guide, examiner): requirements come only from the first.
  • Find them by following the process, the data, the money and the approvals, and by asking "Who else should we talk to?"
  • Record them in a stakeholder register; sort them on a power and interest grid to decide how to involve each.
  • Describe each user class: how many, what they know, how and where they will use the system, what matters most.

Questions you must be able to answer

1. Define a stakeholder, and give four kinds with an example of each from a canteen system. A stakeholder is anyone with an interest in, influence over, or who is affected by the system. Primary users, the students who order; operators, the counter staff who run the day's orders; owners, the canteen owner who decides whether it is used; affected parties, the teachers whose students arrive on time.

2. Why keep the project guide off the system's stakeholder list? Because the guide's interest is in the project as a piece of academic work, not in using the system. Requirements must come from the people who will use, run or be affected by the software; a feature added only because the guide wanted it is not a requirement of the system.

3. What is a power and interest grid, and how is it used? A two-by-two grid placing each stakeholder by their power to decide or block and by how much the system affects them. It decides how each is involved: manage closely, keep satisfied, keep informed, or monitor. It does not decide whose needs matter.

munotes.in30

Stakeholder Identification

4. How did the worked team find the kitchen staff, and what difference did it make? By asking the counter staff "Who else should we talk to?" at the end of his interview. The head cook's interview showed that the kitchen needed the number of each dish ordered for each pickup slot, which became the kitchen list requirement and made the reduction of wasted food possible.

5. What is a user class, and where is it written down? A group of users who use the system in the same way and have the same needs, described by how many there are, what they know, how and where they use the system and what matters most to them. User classes are written in the SRS under user classes and characteristics.

Contents This chapter on its own page

munotes.in31

Chapter Six

Technical Feasibility

Syllabus topic Module 1, "Problem Identification & Feasibility Study: ... Feasibility analysis (technical, economic, operational)", the first of the three.

In one line

Technical feasibility asks whether this team can build and run this system, with the technology, skills, machines and network actually available to it, within the time it has; you answer it item by item, and you record every risk you find with what you will do about it.

In the wording to use when asked: technical feasibility is the assessment of whether the proposed system can be developed and operated with available technology, hardware, software, network infrastructure and technical skills, within the project's constraints, together with the identification of technical risks and their mitigation.

A feasibility study, and where this chapter fits

A feasibility study is a short, early investigation that answers one question: should this project go ahead as proposed? It is done before the heavy investment of time, so that a project that cannot succeed is stopped or reshaped while that is still cheap.

MU names three kinds of feasibility, "technical, economic, operational", and this book gives each a chapter:

KindThe questionChapter
Technicalcan we build it and run it?this one
Economicare the benefits worth the costs?7
Operationalwill it be used, and will it work in the way people actually work?8

Chapter 8 also adds two questions that textbooks often list beside these three, legal feasibility and schedule feasibility, and ends with the feasibility report that brings all of them together.

The answer to each question is not simply yes or no. It is usually "yes, if", and the "if" is the valuable part: the conditions, the limits and the risks you will carry into the rest of the project.

The questions technical feasibility asks

Work through these in order. Each gets a short, honest answer in your study.

1. Is the technology available and proven? The languages, frameworks, database and tools you need must exist, be free or affordable, and be mature enough that problems have known answers. A student project is not the place to depend on something released last month.

2. Does the team have the skills, or can it learn them in time? List every technology and every team member, and rate each person's skill honestly. A skill nobody has is a risk, and its mitigation is time set aside in the plan to learn it, early.

3. What hardware does it need? Machines to develop on, a machine to run the server, and the devices users will use. Check that each exists and that you may use it.

4. Can the users reach the system? The network is where many student projects quietly fail. Where will the server be, and can every user reach it from where they will be standing when they use it?

munotes.in32

Technical Feasibility

5. Where will it be hosted, and who will keep it running? On a team member's laptop only during demonstrations, on a college machine, or on a rented server? Chapter 5 asked who will maintain it. This question asks what they will maintain.

6. Must it work with any existing system? Integration with another system, such as a college's student records or a payment service, is often the hardest technical work of all, and frequently impossible for students, who cannot get access.

7. Can it handle the load? Estimate how many people will use it at its busiest moment, and check that the planned technology can serve them. For most student projects the answer is easily yes, but you must show the arithmetic.

8. Can the data be kept safe? What personal data will it hold, and can the team store and protect it properly?

9. What could go wrong technically? Collect the doubts from every question above into a list of risks, each with its likelihood, its impact and what you will do about it.

The skills matrix

A skills matrix answers question 2 at a glance: people down the side, technologies across the top, a rating in each cell. A simple scale is enough:

RatingMeaning
0never used it
1studied it; could follow an example
2has built something small with it
3confident; could help the others

Any column with no 2 or 3 in it is a technology nobody on the team can yet use unaided. Either drop that technology or plan the learning time.

Estimating load: the arithmetic you must show

"Can it handle the load?" is answered with a short calculation, not a feeling. The method has four steps:

  1. How many users a day, from your evidence.
  2. When they arrive: spread over a day, or bunched into a short window?
  3. The peak rate: users per minute at the busiest moment.
  4. The work each user causes: how many requests one user's visit makes.

Multiply the peak rate by the requests per user and compare the result with what the technology can serve. Leave a wide safety margin, because your estimate is rough and real crowds bunch more tightly than averages suggest. Chapter 11 turns the result into a measurable requirement, and Chapter 63 tests it.

The worked project: technical feasibility

The worked team answered the nine questions in its third week.

Technology and skills

They considered three ways of building the system, each built on papers they had already passed:

Candidate stackLearned inForAgainst
PHP with MySQLWeb Technologies, Semester 2simple to host; one of the team had built a small site with itthe team had written no PHP for two years
Node.js with Express and MySQL, pages in plain HTML, CSS and JavaScriptMEAN Stack Development, Semester 4; the database papersall four had built an Express application last semester; one language, JavaScript, on the server and in the browsernone had deployed Express on Linux
A native Android app in Kotlin with FirebaseMobile Application Development, Semester 4a real app on students' phonesthe counter and the owner work on a laptop or a tablet, not only on phones; Firebase is a hosted service outside the team's control
munotes.in33

Technical Feasibility

Their skills matrix settled it:

MemberJavaScriptNode.js and ExpressMySQLHTML and CSSKotlin and AndroidLinux serverGit and GitHub
Aditi2223112
Farhan3332112
Sneha2113201
Rohan2222213

Every column of the Node.js stack has a 2 or a 3. The one weak column is Linux server, and it became the first risk on their list. They chose Node.js with Express and MySQL, with plain pages that work on phones, tablets and laptops alike, and an Android app that wraps those same pages for students who want an app. Chapter 26 records this decision in full.

Hardware

NeedWhat existsVerdict
Four development machinesthe team's own laptops: three on Windows 11, one a MacBookenough; all can run Node.js and MySQL
A machine to run the serverthe IT lab in-charge offered an old desktop in the lab, to be reinstalled with Ubuntu 24.04enough for one canteen, if it stays on during college hours
A device at the counterthe owner has an Android phone; a tablet would be bettera phone works for the trial; a tablet is costed in Chapter 7
Students' devices176 of 180 surveyed have a smartphone with internetenough

Reaching the system

This was the question that nearly changed the project.

A server on a machine in the college lab sits on the college network. A phone connected to the college Wi-Fi can reach it; a phone using mobile data cannot, unless the college makes that machine reachable from the internet, which the IT in-charge would not do for a student project.

So the team checked two things at the canteen itself. First, whether the college Wi-Fi reached it: they walked the canteen with a phone and found a usable signal at every table and at the counter. Second, whether students could join it: every student with a college account could sign in to the Wi-Fi.

That made the system technically feasible on the college Wi-Fi. It became a recorded constraint: during the trial, students must be on the college Wi-Fi to order. The team noted that a public server, reachable from mobile data, is the way to lift that limit later, and Chapter 58 shows how it is done and what it costs.

munotes.in34

Technical Feasibility

Load

The busiest moment is before the break, when orders for the first slots close. The team's estimate:

  1. Users a day: about 212 students, from their observation.
  2. When they arrive: they assumed the worst plausible bunching, half of the day's orders in the last 15 minutes before the first cut-off, which is 106 orders in 15 minutes.
  3. Peak rate: 106 divided by 15 is about 7 orders a minute.
  4. Work per order: they counted the requests one student's visit makes, from signing in to seeing the order number, as roughly 10 requests.

So the peak is about 70 requests a minute, a little more than one a second. A single Node.js process on a modest machine serves far more than that. To leave a wide margin, they set the requirement at 100 students ordering in the same minute, about fourteen times the estimated peak, and planned to prove it with a load test.

Integration and data

The system needs no other system. It does not read the college's student records; instead, students register themselves with a college email address, and only that address's domain is accepted. It holds no personal data beyond a name, a college email address and each student's orders, and no money passes through it.

Technical risks

RiskLikelihoodImpactWhat the team will do
Nobody has deployed Node.js on a Linux serverhighhigh: no demonstrationpractise a deployment in week 8, as soon as the first page runs, not in week 13; Rohan owns it
Two students order the last plate at the same moment and both are acceptedmediumhigh: food promised that does not existdesign the stock update to be safe under concurrency; test it with simultaneous orders
The college Wi-Fi is down at lunchtimelowhigh: nobody can orderthe counter keeps a paper fallback for that day; recorded as a constraint
The lab machine is switched offmediumhighask the IT in-charge for it to stay on during college hours; make the service start by itself when the machine boots
A student on mobile data cannot reach the serverhighmediumstate the Wi-Fi requirement on the order page; public hosting noted for a later release

Verdict: technically feasible, on the college Wi-Fi, with the Node.js, Express and MySQL stack, on condition that the Linux deployment is practised early and that the ordering is made safe under concurrency.

munotes.in35

Technical Feasibility

Do this for your project

  1. List at least two candidate stacks, each tied to papers your team has passed, with its for and against.
  2. Build a skills matrix. Any column without a 2 or 3 is a risk to plan for or a technology to drop.
  3. Check the hardware: development machines, the server, the users' devices.
  4. Go to the place and check the network there. Decide where the server will be, and confirm every user can reach it.
  5. Estimate the peak load in four steps, and set a target with a wide margin.
  6. List what other systems you would need and whether you can get access; if not, redesign around it.
  7. Write the risk table, and end with a verdict of the form "feasible, on condition that".

Mistakes that cost marks

"The project is technically feasible because the technology exists." Existence is not the question. Whether this team, with these skills and these machines, can build and run it, is.

No load arithmetic. Saying the system "can handle many users" proves nothing. Four lines of arithmetic do.

Forgetting the network. A server the users cannot reach is not a working system, however good the code.

Planning to learn the hardest thing last. Deployment learned in the final week is the most common cause of a failed demonstration.

A risk list with no mitigations. A risk you have written down but not planned for is a worry, not a risk assessment.

Quick revision

  • A feasibility study decides whether the project should go ahead as proposed. MU names technical, economic, operational.
  • Technical feasibility asks: technology, skills, hardware, network reach, hosting, integration, load, data safety, risks.
  • A skills matrix rates each member on each technology; a column without a 2 or 3 is a risk.
  • Load is estimated in four steps: users a day, their bunching, the peak rate, requests per user; then a wide margin.
  • Every risk has a likelihood, an impact and a mitigation.
  • The verdict is usually "feasible, on condition that...".

Questions you must be able to answer

1. What does technical feasibility assess? Whether the proposed system can be built and run with the technology, skills, hardware, network and hosting actually available to the team, within the time it has, and what technical risks stand in the way and how they will be handled.

2. What is a skills matrix, and what does a weak column tell you? A table rating each team member's skill in each technology the project needs. A column with nobody rated able to use the technology unaided shows a skill the team lacks, which must be learned early in the plan or avoided by choosing a different technology.

munotes.in36

Technical Feasibility

3. Show how the worked team estimated its peak load. About 212 students order in a day; assuming half of them order in the last 15 minutes before the first cut-off gives 106 orders in 15 minutes, about 7 a minute; at roughly 10 requests per order that is about 70 requests a minute. The target was then set at 100 students ordering in the same minute, about fourteen times the estimate, as a safety margin.

4. Why did hosting on a college machine make the college Wi-Fi a requirement? Because a machine on the college network can be reached only by devices on that network, and the college would not expose it to the internet. Students on mobile data could not reach it, so ordering was made feasible on the college Wi-Fi, after the team checked that the signal reached every part of the canteen.

5. What form should the conclusion of a technical feasibility study take? A verdict with its conditions, such as "feasible with this stack, on the college Wi-Fi, provided deployment is practised early and ordering is made safe under concurrency", followed by the risk table that supports it.

Contents This chapter on its own page

munotes.in37

Chapter Seven

Economic Feasibility

Syllabus topic Module 1, "Problem Identification & Feasibility Study: ... Feasibility analysis (technical, economic, operational)", the second of the three.

In one line

Economic feasibility asks whether what the system will save or earn is worth what it will cost to build and run; you list every cost, one-time and running, list every benefit, turn what you can into rupees, and work out how long the system takes to pay for itself.

In the wording to use when asked: economic feasibility is the evaluation of a proposed system's costs against its expected benefits, usually through a cost-benefit analysis and a payback period, to determine whether the investment is justified.

Why a student project needs this at all

A student project costs its builders nothing in cash, so it is tempting to write "the project is economically feasible because all the software is free" and move on. That answers the wrong question.

The question is not whether you can afford to build it. It is whether the owner of the problem should adopt it: whether the canteen owner, the housing society or the clinic gains more than it spends by using your system. Even a free system has costs for them: a device to run it on, somebody's time to set it up and learn it, a server that must stay on. And a system nobody gains from will not be used, however cleverly it is built.

This is also where many student reports make an arithmetic mistake that an examiner notices, and this chapter shows it and corrects it.

Costs

Costs come in two kinds, and both must be listed.

One-time costs are paid once, to get the system into use:

  • development: the effort of building it;
  • hardware: any machine or device bought for it;
  • software: licences, if any are not free;
  • setup: installing, configuring, entering the first data;
  • training: teaching the users.

Running costs are paid for as long as the system is used:

  • hosting: a server or a rented machine, and its electricity;
  • network: internet access, a domain name;
  • maintenance: somebody's time to back it up, update it and fix it;
  • consumables and services: SMS, printing, anything paid per use.

Development effort: count it, even if nobody pays

For a student project the development effort is paid in hours, not rupees. Record it anyway, as hours, because it is real effort and because a business adopting a similar system would have to pay someone for it. Do not invent an hourly rate to turn it into money unless someone has given you one; an invented figure in a cost table undermines every real figure beside it.

Benefits

Benefits also come in two kinds.

Tangible benefits can be measured in money: sales recovered, costs saved, fines avoided, staff time freed for other paid work.

munotes.in38

Economic Feasibility

Intangible benefits are real but cannot honestly be priced: time returned to people, less stress, better service, fewer mistakes, a better reputation. Measure them in their own units, such as hours or complaints, and do not force a rupee value on them.

Revenue is not benefit

This is the mistake to avoid. When a system helps a business sell more, the business does not gain the full price of each extra sale. It gains the profit on it: the price minus what the sale cost to make. A canteen that sells one more Rs 68 lunch has spent something on the ingredients, the gas and the cook's time, and gains only what is left.

Counting revenue as benefit makes every payback look several times faster than it is. The worked example below shows the mistake and then the corrected figure.

The measures

Cost-benefit analysis

Put the costs and the benefits side by side, over the same period, and compare. If benefits exceed costs over a period the owner cares about, the project is economically feasible.

Payback period

The payback period is the time the benefits take to repay the one-time cost:

payback period = one-time cost / benefit per period

It is the most useful single measure for a small system, because it answers the owner's real question: "how long before this has paid for itself?"

Return on investment, and money over time

For large projects running over years, analysts also compute the return on investment, the net gain as a percentage of the cost, and the net present value, which discounts money received in future years because a rupee next year is worth less than a rupee today. Both matter when payback takes years. When payback takes weeks, as it usually does for a well-chosen student project, they add little, and it is enough to say so.

Sensitivity: what if you are wrong?

Every benefit figure is an estimate. A careful analysis asks how the conclusion changes if the estimate is too optimistic, for example if only half the hoped-for benefit arrives. If the project is still worth doing under the pessimistic figure, the conclusion is robust. This is called sensitivity analysis, and one extra line of it makes a feasibility study far more convincing.

The worked project: economic feasibility

What the owner told them

In her interview (Chapter 4), the owner, Lata Pawar, gave the team four figures, all recorded as her estimates:

  • the average lunch bill is Rs 68;
  • her margin is about 25 per cent of the bill;
  • about Rs 600 of food is thrown away on an ordinary day, measured at what it cost her to make;
  • the canteen is open about 180 college days a year.
munotes.in39

Economic Feasibility

One-time costs

ItemNoteRs
Android tablet for the counterthe owner's quote from a local shop9,500
Serverthe college's existing lab desktop0
SoftwareNode.js, MySQL Community Server, and the tools, all free0
Setup and first datathe team enters the menu; about 2 hours0
Trainingone session with the counter staff and the owner0
Total9,500

The development effort is recorded separately, in hours: 240 person-hours, 60 for each of the four members, as MU allots.

Running costs

ItemNoteRs a day
Hosting and electricitythe lab machine is already on during college hours0
Networkthe college Wi-Fi, already paid for0
Maintenancethe IT lab in-charge's time; not charged0
SMS and other paid servicesnone; SMS is out of scope0
Total0

Benefits: the first attempt, and what was wrong with it

Aditi's first draft of the benefits looked like this:

BenefitWorkingRs a day
Recovered saleshalf of the 31.2 students who leave, at Rs 68 each1,060.80
Food no longer thrown awaya third of the Rs 600 wasted200.00
Total1,260.80

It gave a payback of 9,500 divided by 1,260.80, which is about 7.5 days. Farhan questioned the first line: the canteen does not keep Rs 68 from a sale, it keeps its margin. The first line was revenue, and the benefit is the profit.

Benefits: corrected

Half of the 31.2 students is 15.6 students a day. Each sale of Rs 68 at a 25 per cent margin leaves Rs 17 of profit.

BenefitWorkingRs a day
Profit on recovered sales15.6 students at Rs 17 of profit each265.20
Food no longer thrown awaya third of the Rs 600 wasted, at cost200.00
Total465.20

The waste line was already right, because the Rs 600 was measured at cost: food not thrown away is money not spent.

Payback, and a year

payback period = 9,500 / 465.20 = 20.42 days

So the tablet pays for itself within the 21st college day, a little over four weeks of college. Over a year of 180 college days, the benefit is 180 multiplied by 465.20, which is Rs 83,736, against a one-time cost of Rs 9,500.

The corrected payback is nearly three times longer than the first draft's, and it is still short. That is the honest conclusion, and it is the one that survives an examiner's question about margins.

Sensitivity

The team asked what happens if they are wrong about the recovered sales, and only a quarter of the students who leave today come back instead of half.

munotes.in40

Economic Feasibility

A quarter of 31.2 is 7.8 students a day, at Rs 17 of profit each, which is Rs 132.60. With the Rs 200 saved on waste, the benefit is Rs 332.60 a day, and the payback is 9,500 divided by 332.60, about 28.6 days. Still within six weeks of college. The conclusion does not depend on the optimistic figure.

Intangible benefits

BenefitHow it is measured
Time returned to students142 of 180 students (78.9 per cent) said they would pre-order, about 167 of the 212 served each day. If each waits 3 minutes instead of 16, that is 13 minutes each and 2,171 minutes, about 36 hours, of students' time a day
Fewer students late for the 13:10 lecturethe teachers are asked once, after the trial
A calmer counterthe counter staff's own view, after the trial
The kitchen cooks to orders, not guessesthe kitchen's own view, and the waste figure

The team left all four unpriced. Thirty-six hours of students' time a day is a striking number without a rupee sign beside it.

Verdict: economically feasible. For a one-time cost of Rs 9,500 and no running cost, the canteen gains an estimated Rs 465.20 a day, paying back within about four weeks of college days, and within six weeks even if only a quarter of the lost sales return.

Do this for your project

  1. Ask the owner of the problem for the figures you need, and record each as their estimate: prices, margins, volumes, days of operation, what the problem costs them now.
  2. List one-time costs and running costs, each with a note of where the figure came from.
  3. Record the development effort in hours; do not invent a rate for it.
  4. List tangible benefits in rupees, as profit or saving, never as revenue, and intangible benefits in their own units.
  5. Compute the payback period, and a year's benefit.
  6. Redo the payback with a pessimistic benefit, and say whether the verdict survives.
  7. End with a one-sentence verdict that states its conditions.

Mistakes that cost marks

"Economically feasible because the software is free." It answers whether the students can afford to build it, not whether the owner should adopt it.

Revenue counted as benefit. The most common arithmetic error in feasibility studies, and an examiner who spots it will doubt every other figure.

Invented figures. A cost or rate with no source, dropped in to make a table look complete, weakens the real figures around it.

Intangibles given a price. "Student satisfaction worth Rs 50,000" is a guess dressed as a number.

No pessimistic case. A conclusion that depends on everything going well is a hope.

Quick revision

  • Economic feasibility compares costs with benefits.
  • Costs: one-time (development, hardware, software, setup, training) and running (hosting, network, maintenance, paid services).
  • Record student effort in hours; do not invent a rate.
  • Benefits: tangible in rupees, intangible in their own units.
  • Revenue is not benefit: count the profit on extra sales, and savings at cost.
  • Payback period = one-time cost divided by benefit per period.
  • Sensitivity analysis: redo it with a pessimistic benefit; a robust verdict survives.
munotes.in41

Economic Feasibility

Questions you must be able to answer

1. Distinguish one-time costs from running costs, with an example of each for a canteen system. One-time costs are paid once to bring the system into use, such as a tablet bought for the counter; running costs are paid for as long as it is used, such as rented hosting or paid SMS messages.

2. Why is the price of a recovered sale not the benefit of recovering it? Because the business spent money to make what it sold. Its gain is the profit, the price less the cost of making the sale. Counting the full price overstates the benefit and makes the payback look far quicker than it is.

3. Compute the worked project's payback period, and explain each figure. The one-time cost is Rs 9,500 for the counter tablet. The daily benefit is Rs 265.20 of profit on 15.6 recovered sales, at Rs 17 each, plus Rs 200 of food no longer wasted, a total of Rs 465.20. Payback is 9,500 divided by 465.20, about 20.4 days.

4. What is sensitivity analysis, and what did it show for the worked project? Recomputing the result with a less favourable estimate to see whether the conclusion still holds. With only a quarter of the lost sales recovered, the benefit falls to Rs 332.60 a day and the payback rises to about 28.6 days, so the project remains worthwhile.

5. Why did the worked team not put a rupee value on the time returned to students? Because there is no honest price for a student's time, and a guessed figure would be a false precision that weakens the real figures. They measured it in its own unit instead: about 36 hours of students' time a day.

Contents This chapter on its own page

munotes.in42

Chapter Eight

Operational Feasibility and the Feasibility Report

Syllabus topic Module 1, "Problem Identification & Feasibility Study: ... Feasibility analysis (technical, economic, operational)", the third of the three; and Course Outcome OC 1, "prepare structured project documentation including SRS and feasibility reports".

In one line

Operational feasibility asks whether the system will actually be used, and will work, in the way the people and the organisation really operate; this chapter also asks the two questions the three names leave out, whether it is lawful and whether it can be finished in time, and then puts every answer into a feasibility report.

In the wording to use when asked: operational feasibility is the assessment of whether a proposed system fits the organisation's processes, will be accepted and used by its users, can be operated and supported after installation, and will solve the problem in practice; a feasibility report presents the technical, economic, operational, legal and schedule findings with a recommendation.

Why a system that works can still fail

Many systems that were technically sound and economically sensible were never used. They asked people to change how they worked in ways nobody had agreed to, or made someone's job harder at the busiest moment of their day, or were left with nobody to look after them once their builders left.

Operational feasibility is the study of exactly those failures, before they happen. It is about people and process, not about code. That is why it cannot be answered from a desk: it needs the stakeholders you listed in Chapter 5, and it needs you to watch the place at its busiest again, this time imagining your system in it.

The questions operational feasibility asks

1. Does it fit the way the work is done? Write the process as it is now, step by step, and then as it will be with the system. Every step that changes is a step someone must be willing and able to change.

2. Can the users use it? Their skills, their devices, their time, and the conditions they will be working in: noise, crowds, one free hand.

3. Will they want to? Who gains from the change, and who loses something: a habit, a skill that stops mattering, control over their own work, the chance to blame the system. People who lose something resist, often quietly.

4. What happens when things go wrong? The network fails, a student orders and never comes, the kitchen runs out of something the system says is in stock. A system that only works when everything goes right is not operationally feasible.

5. Who will run and support it? Somebody enters the daily data, somebody resets a forgotten password, somebody restarts the server. Name them.

6. Do the people in charge support it? Without the owner's and the institution's support, a system is not adopted, however good it is.

7. How will people learn it? Training, a one-page guide, a trial period.

munotes.in43

Operational Feasibility and the Feasibility Report

Before and after: the most useful table in the study

The clearest way to answer question 1 is a table of the process before and after.

For the worked project:

StepNowWith Canteen Pre-order
Deciding what to eatat the counter, while the queue waitson the phone, before the break
Ordering and payingin the queue at the counter, both at onceordering on the phone; paying at the counter on collection
The kitchen knows what to makeguessed in the morninga list of what has been ordered for each slot, by 12:20
Waiting for foodat the counter, in a crowdonly until the chosen slot
Collectingshout the order across the countergive the order number
Students without a phone, or on mobile data onlyqueue as nowqueue as now: walk-in sales continue
The day's salescounted from slips at closingthe owner's daily report, for pre-orders

Two decisions are visible in the table, and both came from studying operations rather than technology.

Walk-in sales continue. Four of the 180 students surveyed had no smartphone with internet, and some students will not be on the college Wi-Fi. The system does not replace the counter; it takes pre-ordered lunches out of the queue, which shortens the queue for everyone else too.

The stock in the system is the owner's allocation for pre-orders. The kitchen cooks for walk-in customers as well. So each morning the owner decides how many portions of each dish to offer for pre-order, and enters that number as the day's stock. When it is gone, the dish shows as sold out in the app and is still sold at the counter while it lasts.

The worked project: operational feasibility

Users' ability. Students use phones all day (Chapter 5's user classes). The counter staff member, Ganesh More, uses a phone but little else; so every counter action is a single large button, and nothing needs typing during the break. The owner uses the system in the morning and evening, at quiet times.

Willingness, and the concerns the team heard:

WhoConcernWhat the design does about it
Ganesh, at the counter"Now I have to watch a screen as well as the queue"the counter screen shows one slot at a time, oldest first, one button per order
Ganesh"Students will say they ordered when they did not"every order has a number, and the counter's list is the truth
The owner"Students will order and not come, and I will waste food"no-shows are recorded on the counter screen; the daily report shows how many; a penalty rule is left for the college to decide later
The head cook"We still have to cook for the counter"the kitchen list shows pre-orders only, and walk-in cooking continues as before
Students"What if I am late for my slot?"the order waits at the counter until the end of the break; it is marked not collected only then
munotes.in44

Operational Feasibility and the Feasibility Report

When things go wrong:

FailureWhat happens
The college Wi-Fi fails at lunchtimenobody can order; the counter works from walk-ins as today, and pre-orders already placed are on the counter screen of the lab machine's network
The server is offthe same fallback; the service is set to start by itself when the machine boots (Chapter 60)
A dish runs out in the kitchen while pre-orders exist for itthe counter tells those students and offers another dish; the owner reduces the next day's allocation
A student forgets their passwordthe owner can create a new account for them after checking their college identity card; a self-service reset is a later release

Support after the semester. The IT lab in-charge agreed to keep the machine on during college hours and to restart it if needed. The team writes a server guide for him (Chapter 60) and a user manual for the owner and the counter (Chapter 67).

Management support. The owner agreed to a two-week trial. The principal's office agreed after a letter and a short demonstration, on condition that only college email addresses may register.

Training. One half-hour session at the canteen with the owner and the counter staff, and a one-page guide taped beside the counter.

Verdict: operationally feasible, with walk-in sales continuing beside it, the owner allocating stock for pre-orders each morning, and the IT in-charge supporting the machine.

Two more questions: is it lawful, and is there time?

MU names three kinds of feasibility. Two more are often added, and a careful study answers both, because either can stop a project on its own.

Legal feasibility

Ask what the system collects about people, and what the law requires of whoever runs it. For most student projects the relevant law is about personal data.

India's Digital Personal Data Protection Act, 2023 (No. 22 of 2023) defines personal data as "any data about an individual who is identifiable by or in relation to such data", and calls the person who "determines the purpose and means of processing of personal data" the Data Fiduciary. For the worked project that is whoever runs the system: the canteen owner.

The Act is coming into force in stages. By a notification of 13 November 2025 (G.S.R. 843(E)), its main obligations, in sections 3 to 17, including notice, consent and the general obligations of a Data Fiduciary, come into force eighteen months after that date, which is 13 May 2027; one sub-section of section 6, about consent managers, comes in a year after the notification. A system built now should nevertheless be designed to meet them, because it will still be running then. Two of those obligations shape the worked project directly. Section 8(5) requires a Data Fiduciary to "protect personal data in its possession or under its control ... by taking reasonable security safeguards to prevent personal data breach", and section 8(7) requires personal data to be erased once "the specified purpose is no longer being served".

munotes.in45

Operational Feasibility and the Feasibility Report

Until then, the Information Technology Act, 2000 already applies. Its section 43A makes a "body corporate", which its Explanation defines to include a "sole proprietorship or other association of individuals engaged in commercial or professional activities", liable to pay compensation if it is negligent in "implementing and maintaining reasonable security practices and procedures" for sensitive personal data. The rules made under it in 2011 list the first kind of sensitive personal data as a password.

So the law, as it stands and as it is coming, points the same way for a system like this:

  • collect only what the purpose needs: the worked project stores a name, a college email address, a password hash and the orders, and nothing else, no phone number and no payment details;
  • tell users what it is for: the registration page says what is stored and why;
  • protect it with reasonable security: passwords are stored only as salted hashes (Chapter 46), access is by role, and the server is reached over the college network only;
  • do not keep it longer than needed: orders older than the current term can be deleted by the owner, and accounts of students who leave can be removed.

The team also needed permission to use the college's machine and its email domain, which is a matter of the college's rules rather than the law, and they had it in writing.

This book explains what the texts say so that you can design responsibly; it is not legal advice, and a system handling personal data for real should have its legal position checked by someone qualified.

Schedule feasibility

Can the project be finished in the time available? Chapter 17 computes the worked team's schedule: 65 working days of work on the longest chain of tasks, against a semester of 15 weeks, which is 74 working days once the Gandhi Jayanti holiday on 2 October is taken out. That leaves 9 working days of slack for college events, examinations in other papers, and the things that go wrong. Feasible, with a margin, though not a large one.

The feasibility report

MU's first Course Outcome for this paper asks you to "prepare structured project documentation including SRS and feasibility reports". The feasibility report is where the findings of Chapters 6, 7 and 8 come together. It is short, because it summarises; the working stays in your notes.

munotes.in46

Operational Feasibility and the Feasibility Report

A dependable structure:

  1. The problem: the problem statement from Chapter 4.
  2. The proposed solution, in a paragraph, and the alternatives considered.
  3. Technical feasibility: the verdict, the stack, the key conditions.
  4. Economic feasibility: costs, benefits, payback, sensitivity.
  5. Operational feasibility: the process before and after, concerns and answers, support.
  6. Legal and schedule feasibility.
  7. Risks: the most serious ones, with mitigations.
  8. Recommendation: go ahead, go ahead with conditions, or do not go ahead.

Three to five pages is plenty. Much of it will be reused in the project proposal (Chapter 33), which is why it is worth writing well.

The worked project: the feasibility report

Feasibility Report: Canteen Pre-order. Version 1.0, 19 August 2026. Prepared by Aditi Kulkarni, Farhan Shaikh, Sneha Nair and Rohan D'Souza. Guide: Prof. S. Iyer.

1. The problem. Students have a 40-minute lunch break and one canteen counter at which they both order and wait. Over five days we counted an average of 212 students served and 31.2 leaving the queue without buying each day, and timed waits averaging 16 minutes and reaching 27. In a survey, 131 of 180 students said the queue makes them late for the 13:10 lecture at least once a week. The owner estimates that about Rs 600 of food is thrown away each day because the kitchen cannot know demand in advance.

2. Proposed solution and alternatives. A web application, with an Android app, through which students order before the break for a ten-minute pickup slot and pay at the counter on collection; a counter screen for the day's orders; a kitchen list; and the owner's menu, stock and daily report. Alternatives considered and rejected: a second counter (cost of staff), paper tokens (ordering still queues), a shared online form (no stock control or status), a general delivery platform (built for delivery, not collection).

3. Technical feasibility. Feasible with Node.js, Express and MySQL, which all four members have used, on a lab desktop running Ubuntu 24.04, reached over the college Wi-Fi, whose signal we confirmed throughout the canteen. Estimated peak about 7 orders a minute; we will test at 100 in a minute. Conditions: the Linux deployment is practised by week 8, and ordering is made safe when many students order the last portions at once.

4. Economic feasibility. One-time cost Rs 9,500, for a tablet at the counter; no running cost. Estimated benefit Rs 465.20 a day: Rs 265.20 of profit on half the lost sales at the owner's 25 per cent margin, and Rs 200.00 of food not wasted. Payback about 20 days of college; about 29 days if only a quarter of lost sales return. About 36 hours of students' time returned daily, unpriced.

munotes.in47

Operational Feasibility and the Feasibility Report

5. Operational feasibility. Fits the canteen with two decisions: walk-in sales continue, and the owner allocates each dish's pre-order stock every morning. Concerns of the counter, the kitchen and the owner are answered in the design. The IT lab in-charge supports the machine; one training session and a one-page guide for the counter.

6. Legal and schedule feasibility. We store only a name, a college email address, a password hash and orders, with no payment data. Passwords are sensitive personal data under the Information Technology rules of 2011, and are stored only as salted hashes; the design follows the obligations of the Digital Personal Data Protection Act, 2023 that come into force on 13 May 2027. The college has permitted the use of its machine and email domain. The schedule needs 65 working days of the 74 available.

7. Main risks. Linux deployment inexperience (practise early); simultaneous orders for the last portions (safe stock updates, tested); Wi-Fi failure at lunchtime (walk-in fallback); students who order and do not come (recorded; policy for the college).

8. Recommendation. Proceed, on the conditions in sections 3 and 5, with a two-week trial agreed with the owner.

Do this for your project

  1. Write the process before and after, step by step, with the people who do each step.
  2. Take the table to the users and ask what worries them. Record each concern and what your design does about it.
  3. List what can go wrong in operation, and what happens then.
  4. Name who will run and support the system after the semester.
  5. List the personal data you will store, and cut anything the purpose does not need.
  6. Check the schedule against the weeks you have.
  7. Write the feasibility report in eight short sections, and end with a recommendation.

Mistakes that cost marks

Operational feasibility answered in one line. "Users will find it easy to use" is an assumption. The before-and-after table and the list of concerns are evidence.

A system that replaces a process everyone relies on, with no fallback when it fails.

Personal data collected because a form had room for it. Every field you store is a field you must protect.

Treating a law as in force when it is not, or as irrelevant because it is not yet in force. Say what applies now and what is coming, and design for both.

A report with no recommendation. A feasibility study exists to reach a decision.

munotes.in48

Operational Feasibility and the Feasibility Report

Quick revision

  • Operational feasibility: fit with the process; users' ability; users' willingness; what happens when things go wrong; who runs and supports it; management support; training.
  • The before-and-after process table is the core evidence.
  • Resistance comes from people who lose something; record their concerns and answer each in the design.
  • Legal feasibility: the personal data held and the law on it. DPDP Act 2023: sections 3 to 17 in force from 13 May 2027 (G.S.R. 843(E)); until then IT Act s.43A and the 2011 rules, which count a password as sensitive personal data.
  • Schedule feasibility: the longest chain of work against the weeks available.
  • The feasibility report: problem, solution and alternatives, technical, economic, operational, legal and schedule, risks, recommendation.

Questions you must be able to answer

1. What does operational feasibility assess, and why can a technically sound system fail it? Whether the system fits the organisation's processes and will be accepted, used and supported by its people. A technically sound system fails it when it disrupts how people work, makes someone's busiest moment harder, has no fallback when something fails, or has nobody to look after it.

2. How does a before-and-after process table help? It shows every step that the system changes, and therefore every place where someone must change their work. Each changed step is a point to check with the people who perform it, and the table reveals decisions such as keeping walk-in sales beside pre-orders.

3. Why does the worked project let walk-in sales continue? Because some students have no smartphone with internet, some will not be on the college Wi-Fi, and the system must not fail the canteen when the network fails. Pre-ordering takes orders out of the queue; it does not replace the counter.

4. Which law applies today to the passwords the worked project stores, and which is coming? Today, section 43A of the Information Technology Act, 2000, which requires reasonable security practices for sensitive personal data, and the 2011 rules under it, which list passwords as sensitive personal data. Coming, the Digital Personal Data Protection Act, 2023, whose sections 3 to 17, including the Data Fiduciary's duty to take reasonable security safeguards, come into force on 13 May 2027.

5. List the sections of a feasibility report. The problem; the proposed solution and the alternatives considered; technical, economic and operational feasibility; legal and schedule feasibility; the main risks with their mitigations; and a recommendation.

Contents This chapter on its own page

munotes.in49

Chapter Nine

Requirement Engineering: Eliciting, Recording and Checking Requirements

Syllabus topic Module 1, "Requirement Engineering", the process as a whole, before its five named items ("Functional requirements specification, Non-functional requirements, Use-case analysis, Requirement prioritization, Constraints and assumptions"), each of which has a chapter of its own.

In one line

Requirement engineering is the work of finding out what a system must do and how well, from the people who need it, writing that down so precisely that it can be built and tested, checking it with those people, and keeping it up to date as it changes.

In the wording to use when asked: requirement engineering is the systematic process of eliciting, analysing, specifying, validating and managing the requirements of a software system, so that the specified system meets the needs of its stakeholders and every requirement can be traced from its source to its implementation and its test.

What a requirement is

A requirement is a statement of something the system must do, or a quality it must have, that a stakeholder needs and that can be checked in the finished system.

Requirements come in a few kinds, and each kind has its own chapter:

KindSaysExample from the worked projectChapter
Functional requirementwhat the system must doa student can cancel an order while it is still placed10
Non-functional requirementhow well it must do it95 per cent of order requests are answered within 1 second at the lunch rush11
Constrainta limit on how it may be built or runit must run on the college network14
Assumptionsomething taken to be true that the design relies onthe owner enters each day's stock before 11:0014

One more word appears often. A business rule is a rule of the organisation that the system must enforce, such as "orders for a slot close 15 minutes before it". Business rules usually become functional requirements, and their source is the organisation, not the software.

Why it is the stage where projects fail

When software disappoints its users, the cause is more often a requirement than a bug. Either the requirement was never found, or it was written so vaguely that the builders understood something different from what was meant, or it changed and the change never reached the people building.

Every one of those failures is cheap to prevent early and expensive to repair late. A requirement found missing during an interview costs a line of text. The same requirement found missing at the demonstration costs a redesign.

The five activities

Requirement engineering is usually described as five activities. They overlap and repeat, but they happen in roughly this order.

ActivityThe question it answersIts product
Elicitationwhat do the stakeholders need?notes, recordings, a list of candidate requirements
Analysiswhich of these are real, complete, consistent and possible?a cleaned, classified list, with conflicts resolved
Specificationhow exactly do we write them down?the SRS (Chapter 34)
Validationis this really what they need?reviewed and approved requirements
Managementhow do we handle change, and keep track?a version history and a traceability matrix
munotes.in50

Requirement Engineering: Eliciting, Recording and Checking Requirements

Elicitation: getting requirements out of people

"Elicit" means to draw something out. The word is chosen deliberately: requirements are rarely handed over in finished form. People know their work, not your system, and they describe problems and habits, not requirements. Your job is to draw the requirements out of what they say and do.

The techniques

TechniqueWhat it isGood forWeak at
Interviewa planned conversation with one stakeholder or a small groupthe reasons behind things; the exceptions; how work really goesanything the person does not notice they do
Questionnairewritten questions to many peoplehow common something is; preferences across a groupwhy; anything not thought of when the questions were written
Observationwatching the work being done, where it is donewhat people actually do, including what they would never think to mentionwhy they do it; rare events
Document studyreading the forms, registers, slips and rules the place already useswhat information matters, and in what formwhether the documents are followed
Workshopbringing several stakeholders together to agree requirementsconflicts between groups, settled in one roomshy stakeholders, who say nothing in front of their boss
Prototypea sketch or mock-up of screens, shown to users for reactionwhether you have understood; details of screensthe rules behind the screens

No single technique is enough. Interviews tell you what people think happens; observation tells you what does happen; documents tell you what is supposed to happen. Where the three disagree, you have usually found something important.

Running an interview well

Most student interviews fail in one of three ways: no preparation, questions that suggest their own answers, and no record. A good interview has a shape.

  1. Prepare. Know who you are meeting and what you need from them. Write eight to twelve questions, most of them open.
  2. Open with their work, not your system. "Walk me through a normal lunch break at the counter" produces requirements. "Would you like an app?" produces politeness.
  3. Ask for examples. "Tell me about the last time an order went wrong." A real example carries detail no general answer does.
  4. Ask about exceptions. "What happens when a dish runs out? When a student disputes an order? When the power fails?" Exceptions are where systems break.
  5. Do not design in front of them. Listen; the design comes later.
  6. End with two questions. "Is there anything I should have asked and did not?" and "Who else should we talk to?" (Chapter 5).
  7. Write up the same day, while you remember, and send the key points back to the person to check.
munotes.in51

Requirement Engineering: Eliciting, Recording and Checking Requirements

Recording what you hear

Every candidate requirement goes into one list, one row each, with enough information to trace it and argue about it later.

FieldWhy it is there
IDso it can be referred to without quoting it
Statementthe requirement itself, one requirement per row
Sourcewho said it, or where it was seen, and when
Kindfunctional, non-functional, constraint, assumption, business rule
Prioritydecided in Chapter 13
Statusproposed, agreed, rejected, deferred, changed

The source column is the one students leave out and examiners ask about. "Where did this requirement come from?" should always have an answer with a name and a date.

Analysis: turning notes into requirements

Raw notes contain duplicates, contradictions, wishes, solutions dressed as requirements, and gaps. Analysis cleans them up.

  • Merge duplicates. Three people said the menu should show prices; that is one requirement with three sources.
  • Separate problems from solutions. "The counter needs a buzzer" is a solution. The requirement underneath it is "the student must know when the order is ready".
  • Resolve conflicts. When two stakeholders want incompatible things, the conflict goes to the person with the authority to decide, usually the owner, and the decision is written down with its reason.
  • Find the gaps. For every action a user can take, ask what happens when it fails, and whether anybody else needs to know about it.
  • Classify each as functional, non-functional, constraint or assumption.

Validation: checking with the people who know

Validation asks: is this the right system? It is done by putting the written requirements in front of the stakeholders and asking them to confirm or correct them. (Its partner, verification, asks whether the system was built to the requirements; that is testing, in Module 2.)

The usual way is a review: a meeting in which the stakeholders and the team walk through the requirements one by one. Before the review, check each requirement against a short list of qualities. A good requirement is:

  • clear: one meaning only, to every reader;
  • testable: somebody could design a test that it passes or fails;
  • necessary: a stakeholder needs it, and you know who;
  • feasible: it can be built with the time, skills and technology available;
  • consistent: it does not contradict another requirement;
  • complete: it says what happens in the exceptions, not only in the normal case;
  • traceable: it has an identifier and a source.

A requirement that fails one of these is rewritten before the review, not during it.

Management: change and traceability

Requirements change. The owner changes her mind; a test reveals a case nobody thought of; a constraint turns out to be negotiable. That is normal. What is not acceptable is change that nobody records.

munotes.in52

Requirement Engineering: Eliciting, Recording and Checking Requirements

Two tools keep change under control.

A baseline and a version history. Once the requirements are agreed, that version is the baseline. Every later change is made in a new version, with a line in the document's version history saying what changed, why, and who agreed. Chapter 32 sets out the format.

A requirements traceability matrix. A traceability matrix is a table that links every requirement to where it came from and to everything built from it: the use case, the design element, the code, the test. It answers two questions an examiner can ask of any system:

  • Forward: "This requirement: where is it implemented, and how do you know it works?" Follow the row to the right.
  • Backward: "This screen, this table, this test: which requirement is it for?" Follow the columns back to the left. An element with no requirement behind it is either missing a requirement or should not exist.

The matrix is started now, with sources and requirements, and grows a column at each later stage.

The worked project: requirement engineering

Elicitation

In the two weeks after the observation of Chapter 3, the worked team held these sessions:

SessionWithDateTechnique
1Lata Pawar, the owner10 Augustinterview
2Ganesh More, at the counter11 Augustinterview, then two lunch breaks of observation
3The head cook12 Augustinterview in the kitchen
4Six students of different years13 Augustgroup interview
5The IT lab in-charge13 Augustinterview
6The owner's menu board and a week's sales slips10 Augustdocument study

The survey of 180 students (Chapter 4) was already in hand, and so was the observation itself.

An extract of the notes, and what became of it

From Aditi's write-up of session 2, checked with Ganesh the next morning:

During the rush Ganesh takes money with one hand and passes plates with the other. He said he would never type anything at the counter between 12:30 and 13:10. The worst moments are when a student insists they ordered something they did not, and when two students claim the same plate. He wants to see only the orders for the next few minutes, not the whole day. When a student does not come, the food is sold to someone else at the end of the break.

Four candidate requirements came out of that paragraph: every counter action must be a single tap (a non-functional requirement, NFR-7 in Chapter 11); each order needs a number the student can quote (FR-11); the counter must be able to show one slot at a time (FR-14); and an order must be markable as not collected (part of FR-15).

munotes.in53

Requirement Engineering: Eliciting, Recording and Checking Requirements

Analysis: one conflict, settled

In session 1, the owner asked whether students could pay in the app, so that nobody could order and not pay. In session 4, four of the six students said they would not put money into an app run by a canteen. The survey had 124 of 180 preferring to pay at the counter.

The team did not decide this themselves. They took both views to the owner, with the survey numbers, and explained that online payment needs a payment gateway account the project cannot open (Chapter 4). She agreed to leave payment at the counter for this release. The decision went into the list as a rejected requirement with its reason and her name, and online payment went into the out-of-scope table.

The first requirements list, and the traceability matrix begun

After analysis, seventeen functional requirements survived, FR-1 to FR-17, set out in full in Chapter 10. The first two columns of the traceability matrix, with the use case column the next chapters add, looked like this:

SourceRequirementUse case
survey; session 4FR-1 Register with a college email addressUC-1
sessions 1, 4FR-2 Sign in and sign outUC-2
session 1FR-3 The owner creates counter staff accountsUC-13
sessions 1, 4, 6FR-4 Today's menu for everyoneUC-3
sessions 1, 6FR-5 The owner manages menu itemsUC-10
sessions 1, 3FR-6 The owner sets today's pre-order stockUC-11
survey; session 4FR-7 Place an orderUC-4
sessions 1, 3FR-8 Pickup slots and their cut-offUC-4
sessions 1, 3FR-9 Never more than the stock leftUC-4
session 1FR-10 One active order per student per slotUC-4
session 2FR-11 A number for each orderUC-4
session 4FR-12 A student's own orders and their statusUC-5
sessions 1, 4FR-13 Cancel an order while it is placedUC-6
session 2FR-14 The counter's list of orders by slotUC-7
session 2FR-15 Moving an order through its statusesUC-8
session 3FR-16 The kitchen list for a slotUC-9
session 1FR-17 The owner's daily reportUC-12

Every row has a source. Chapter 35 adds the design column, and Chapters 54 and 55 add the tests.

Validation

On 25 August the team walked the owner and Ganesh through the draft SRS at the canteen, after the lunch break. Two changes came out of it. The head cook, invited at Ganesh's suggestion, asked for the cut-off to be 15 minutes before each slot rather than the 10 the draft said, because ten minutes was not enough to cook a biryani; FR-8 was changed and the change recorded in the version history. And the owner asked that an order not collected should not return its food to the stock, because that food had been cooked; that became part of FR-15 and of the rules in Chapter 43.

munotes.in54

Requirement Engineering: Eliciting, Recording and Checking Requirements

Do this for your project

  1. Plan your sessions: who, when, which technique. Use at least three techniques.
  2. Prepare open questions for each interview; always ask about exceptions, and end with "who else?".
  3. Write up each session the same day and send the key points back to be checked.
  4. Put every candidate requirement in one list with an ID, a source and a kind.
  5. Analyse: merge, separate problems from solutions, send conflicts to the person who can decide, and record the decision.
  6. Start the traceability matrix with a source for every requirement.
  7. Hold a validation review with the stakeholders, and record every change it makes.

Mistakes that cost marks

Requirements with no source. "Where did this come from?" deserves a name and a date.

Asking stakeholders to design. "What buttons do you want?" gets opinions about screens; "tell me about the last time something went wrong" gets requirements.

Deciding conflicts yourselves. The team does not own the problem. The owner does, and her decision, recorded, protects you when it is questioned.

Skipping validation. An SRS nobody outside the team has read is a guess about what they need.

Changes made silently. A requirement changed without a version note makes the traceability matrix and the tests wrong without anyone knowing.

Quick revision

  • Requirement engineering = elicitation, analysis, specification, validation, management.
  • Kinds: functional, non-functional, constraint, assumption; a business rule usually becomes a functional requirement.
  • Elicitation techniques: interview, questionnaire, observation, document study, workshop, prototype; use several.
  • Record each candidate with an ID, statement, source, kind, priority, status.
  • Validation asks "is this the right system?"; verification asks "was it built right?".
  • A good requirement is clear, testable, necessary, feasible, consistent, complete, traceable.
  • The traceability matrix links sources to requirements to use cases, design, code and tests, forward and backward.

Questions you must be able to answer

1. Name the activities of requirement engineering and what each produces. Elicitation, which produces notes and a list of candidate requirements; analysis, which produces a cleaned, classified and conflict-free list; specification, which produces the SRS; validation, which produces requirements the stakeholders have reviewed and approved; and management, which produces a version history and a traceability matrix.

2. Why is observation needed as well as interviews? Because interviews report what people believe happens and observation shows what actually happens, including actions people do without noticing and would never mention. Where the two disagree, there is usually an important requirement.

3. What is the difference between validation and verification? Validation checks that the requirements describe the system the stakeholders actually need: building the right thing. Verification checks that the system as built meets its requirements: building the thing right, which is what testing does.

munotes.in55

Requirement Engineering: Eliciting, Recording and Checking Requirements

4. What is a requirements traceability matrix, and what do forward and backward traceability mean? A table linking each requirement to its source and to the use case, design, code and tests that realise it. Forward traceability follows a requirement to its implementation and tests; backward traceability follows any design element, code or test back to the requirement that justifies it.

5. How did the worked team settle the question of paying in the app? The owner wanted online payment and most students did not. The team took both views and the survey figures to the owner, explained that a payment gateway account was not obtainable, and she decided to keep payment at the counter. The decision was recorded with its reason and her name, and online payment was placed out of scope.

6. Turn "the counter needs a buzzer" into a requirement. The buzzer is a solution. The requirement beneath it is that a student must be able to know when the order is ready, which the worked project meets by showing each order's status on the student's orders page, refreshed at least every 15 seconds.

Contents This chapter on its own page

munotes.in56

Chapter Ten

Functional Requirements Specification

Syllabus topic Module 1, "Requirement Engineering: Functional requirements specification".

In one line

A functional requirement states one thing the system must do for a user, in words precise enough that a developer knows what to build and a tester knows how to check it; specifying them means writing each one down in a fixed, testable form, with an identifier and a source.

In the wording to use when asked: a functional requirement defines a behaviour of the system, a function it performs in response to an input or event, including the rules that govern it and the output it produces; the functional requirements specification is the numbered, testable statement of every such behaviour.

What makes a requirement functional

A functional requirement describes behaviour: something the system does when a user or another system asks it to. It usually has three parts, even when they are not written separately:

  • an input or trigger: a student taps Place order;
  • the processing, including the rules: check the slot is open, check the stock, take the stock;
  • an output or result: the order is accepted with a number, or refused with a reason.

Everything that is instead about how well the system does things, how fast, how safely, how easily, on which devices, is a non-functional requirement and belongs in Chapter 11.

How to write one

The usual form is a sentence in which the system shall do something:

FR-13. The system shall let a student cancel their own order while its status is placed, and shall return its items to the stock.

"Shall" marks the sentence as binding. Some teams write "must", which means the same; what matters is using one word consistently, and not mixing in "should" or "may", which readers take as optional.

Seven rules make a functional requirement usable:

  1. One requirement per statement. A sentence with two unrelated "and"s is usually two requirements, and one of them will be forgotten in testing.
  2. Name who acts. "Orders can be cancelled" hides who may cancel them. "A student ... their own order" does not.
  3. Say what happens, not how it is built. "The system shall store orders in MySQL" is a design decision. The requirement is the behaviour the user sees; the design comes later and can change without the requirement changing.
  4. Make it testable. Somebody reading it must be able to design a test it passes or fails.
  5. State the rules exactly. Limits, conditions and exceptions are part of the requirement: "from 1 to 5 of each item", "15 minutes before the slot".
  6. Give it an identifier that never changes, even if the requirement is deleted: FR-1, FR-2 and so on.
  7. Record its source, as Chapter 9 explains.

Words that ruin a requirement

Certain words make a requirement impossible to test. Train yourself to find them in your own drafts.

munotes.in57

Functional Requirements Specification

WrittenWhat is wrongBetter
The system should be fastnot a function; "fast" has no numbera non-functional requirement with a number (Chapter 11)
Ordering shall be easy"easy" cannot be testedthe exact steps of ordering, with a limit on taps (Chapter 11)
Users can manage their orderswhich users? what does "manage" include?cancel (a student, own order, while placed) and change status (the counter) as separate requirements
The system shall handle stock etc."etc." hides the unstated resteach stock behaviour stated separately
The system shall store orders in MySQLa design decisionremoved; the architecture records it
The system shall show relevant items"relevant" to whom, by what rule?"items that can be ordered now: switched on today with stock left"

Other words to hunt for: appropriate, adequate, user-friendly, flexible, efficient, as required, and/or, if possible, normally.

Business rules belong inside

A business rule is a rule of the organisation the system must enforce. Rules like "orders for a slot close 15 minutes before it" or "at most 5 of one item" are not decoration; they are exactly what a tester checks and exactly where the application can be wrong. Write them inside the requirement they govern, with their numbers.

User stories and acceptance criteria

Agile teams often write requirements as user stories:

As a student, I want to order lunch before the break, so that I do not lose my break in the queue.

A user story names who wants something, what, and why. It is a good way to start a conversation, and a poor way to end one, because it leaves out the rules. So each story carries acceptance criteria: concrete conditions that must hold for the story to be done. A common form is given, when, then:

Given the pickup slot 12:40, whose cut-off is 12:25,

when a student tries to order for it at 12:25,

then the order is refused with the message that ordering for 12:40 has closed.

For an SRS in this paper, write the shall-statement form: it is what an examiner expects, and it reads as a specification. Add acceptance criteria in the given, when, then form wherever a rule could be misread. They double as test cases in Module 2 (Chapter 55).

Organising the list

Group the requirements by what the user is trying to do, not by screen and not by the order you thought of them. A group per feature area makes gaps visible: an area with a create but no cancel, or a list but no way to change what is listed.

munotes.in58

Functional Requirements Specification

The worked project: the functional requirements

The worked team's specification, as it stood after the validation review of 25 August. Every rule in it is enforced by the application you will see in Module 2, which is why the numbers are so exact.

Accounts

FR-1. Register. The system shall let a person create a student account by giving a name of 2 to 80 letters, an email address at the college's own domain, and a password of 8 to 128 characters. It shall refuse an email address at any other domain, and an email address that already has an account. (Source: the principal's office, whose condition was that only college addresses may register; the survey.)

FR-2. Sign in and sign out. The system shall let a registered user sign in with their email address and password, keep them signed in for 8 hours or until they sign out, and let them sign out. When the email address or the password is wrong, it shall give the same message for both.

FR-3. Counter staff accounts. The system shall let the owner create a counter staff account with a name, an email address and a password.

The menu

FR-4. Today's menu. The system shall show anyone, signed in or not, today's menu: for each item its name, category, price, whether it is vegetarian, and whether it can be ordered now. An item with fewer than 10 portions left shall show how many are left; an item with none left shall show as sold out.

FR-5. Menu items. The system shall let the owner add a menu item, giving its name, category, price and whether it is vegetarian; change an item's price; and put an item on or take it off today's menu.

FR-6. Today's stock. The system shall let the owner set, for each item, the number of portions offered for pre-order today, from 0 to 1,000.

Ordering

FR-7. Place an order. The system shall let a signed-in student order one or more items that can be ordered now, from 1 to 5 of each item and no more than 10 items in all, for one pickup slot.

FR-8. Pickup slots. The system shall offer the pickup slots 12:30, 12:40, 12:50 and 13:00, on the canteen's own clock, and shall stop accepting orders for each slot 15 minutes before it.

FR-9. Stock. The system shall not accept an order for more of any item than is left, and shall reduce each item's stock by the quantity of every order it accepts. If any item of an order cannot be supplied, no part of the order shall be accepted.

FR-10. One order per slot. The system shall allow a student at most one active order, meaning placed, being prepared or ready, for each pickup slot.

munotes.in59

Functional Requirements Specification

FR-11. Order number. The system shall give each accepted order a number, and show it to the student as soon as the order is accepted.

FR-12. My orders. The system shall show a signed-in student their own orders for today, with each order's number, slot, items, total and status, and shall refresh the status at least every 15 seconds while the page is open.

FR-13. Cancel an order. The system shall let a student cancel their own order while its status is placed, and shall return its items to the stock.

The counter and the kitchen

FR-14. The counter's list. The system shall show counter staff and the owner today's orders, all together or for one chosen slot, with each order's number, the student's name, the items, the total and the status.

FR-15. Moving an order on. The system shall let counter staff and the owner move an order from placed to being prepared, from being prepared to ready, and from ready to collected or to not collected, and shall refuse every other change of status. An order marked not collected shall not return its items to the stock.

FR-16. The kitchen list. The system shall show counter staff and the owner, for a chosen slot today, the total quantity of each item in orders that are placed or being prepared.

FR-16 is worth a note. It was not on the team's first list at all. It exists because the counter staff sent the team to the kitchen, and the head cook explained that what the kitchen needs is not an app but a count of each dish per slot before the rush (Chapter 5).

The owner

FR-17. Daily report. The system shall show the owner, for today or any chosen date, the number of orders in each status, and the quantity and takings of each item in collected orders, with the day's total takings.

Acceptance criteria for the rules most easily misread

The team wrote given, when, then criteria for the four rules that caused the most discussion at validation:

RequirementGivenWhenThen
FR-8the slot 12:40, whose cut-off is 12:25a student orders for it at 12:24the order is accepted
FR-8the same slota student orders for it at 12:25the order is refused: ordering for 12:40 has closed
FR-92 portions of Veg Biryani lefta student orders 1 Veg Thali and 3 Veg Biryanithe whole order is refused, and the Veg Thali stock is unchanged
FR-10a student with a placed order for 12:40the same student orders again for 12:40the second order is refused
FR-15an order that is placedthe counter tries to mark it readythe change is refused; it must be prepared first
munotes.in60

Functional Requirements Specification

Look at the FR-9 row. It says the thali is not taken when the biryani fails. That sentence is easy to miss and hard to build: it needs the database to treat the whole order as one unit that succeeds or fails together, which is what Chapter 45 builds and tests.

Do this for your project

  1. Group your candidate requirements by what the user is trying to do.
  2. Write each as "The system shall...", one behaviour per statement, naming who acts.
  3. Put every limit and condition inside the requirement, with its number.
  4. Hunt for the ruinous words (fast, easy, manage, etc., appropriate) and rewrite each.
  5. Remove anything that is a design decision, and move anything about "how well" to your non-functional list.
  6. Number them FR-1 onward, and never reuse a number.
  7. Write given, when, then criteria for every rule a reader could misunderstand.

Mistakes that cost marks

A list of screens instead of behaviours. "Login page, menu page, cart page" says nothing about what happens on them.

Missing the unhappy paths. A requirement for ordering with nothing about sold-out items, closed slots or duplicates specifies half a system.

Requirements the code does not meet. An examiner who reads your SRS and then uses your application notices the difference. Update the requirement or the code, and record which.

Design in the requirements. "Using a MySQL table" in a requirement ties it to one implementation.

Unnumbered requirements. Without identifiers they cannot be traced, prioritised or tested.

Quick revision

  • A functional requirement is one behaviour: input, processing with its rules, output.
  • Form: "The system shall...", one behaviour, the actor named, the rules and numbers inside, testable, numbered, with a source.
  • Banned words: fast, easy, manage, etc., appropriate, user-friendly, and/or.
  • Business rules go inside the requirements they govern.
  • User stories (as a... I want... so that...) start conversations; acceptance criteria (given, when, then) make them testable.
  • Group by what the user is trying to do; a group with a create and no cancel shows a gap.

Questions you must be able to answer

1. What distinguishes a functional requirement from a non-functional one? A functional requirement states what the system does, a behaviour with an input, processing and an output. A non-functional requirement states how well the system does it, such as its speed, security, usability or compatibility.

2. Rewrite "Users can manage orders" as proper requirements. It hides two different behaviours by two different users: "The system shall let a student cancel their own order while its status is placed", and "The system shall let counter staff move an order from placed to being prepared, from being prepared to ready, and from ready to collected or not collected, and shall refuse every other change of status."

munotes.in61

Functional Requirements Specification

3. Why should a requirement not say how it will be built? Because the requirement is what the user needs and should stay true whatever the design. Tying it to a design decision, such as a particular database table, makes the requirement wrong whenever the design changes, and hides the actual need.

4. What are acceptance criteria, and write one for the worked project's cut-off rule. Concrete conditions that must hold for a requirement or story to be satisfied, often written as given, when, then. Given the slot 12:40 with its cut-off at 12:25, when a student orders for it at 12:25, then the order is refused because ordering for 12:40 has closed.

5. Why is FR-9's last sentence the hardest part of it to build? Because refusing the whole order when any item cannot be supplied means the stock taken for the earlier items must be put back automatically. The database must treat the order as a single transaction that either succeeds completely or leaves nothing changed.

Contents This chapter on its own page

munotes.in62

Chapter Eleven

Non-Functional Requirements

Syllabus topic Module 1, "Requirement Engineering: ... Non-functional requirements".

In one line

A non-functional requirement states how well the system must do what it does, how fast, how securely, how reliably, how easily and on what, and it is only a requirement when it carries a number and a way to check it.

In the wording to use when asked: a non-functional requirement specifies a quality attribute or constraint on the system's operation, such as performance, reliability, security, usability or portability, stated with a measurable criterion and a method of verification, as distinct from a functional requirement, which specifies a behaviour.

Why they decide whether a system is used

A system can do every right thing and still fail. An ordering page that takes twenty seconds to load at 12:14, when everyone is ordering, does exactly what FR-7 says and is useless. A system that stores passwords in plain text does what FR-2 says and is a liability. A counter screen that needs typing during the rush does what FR-15 says and will be abandoned by the second day.

Non-functional requirements are also where student projects are weakest, because they are easy to write badly. "The system shall be fast, secure and user-friendly" appears in thousands of project reports and means nothing, because nobody can test it. This chapter is about writing ones that can be tested.

A map of qualities: ISO/IEC 25010

You do not have to invent the list of qualities a system can have. The international standard ISO/IEC 25010 defines a product quality model for software. Its current edition, ISO/IEC 25010:2023, replaced the 2011 edition, and ISO describes its model as "composed of nine characteristics", each divided into sub-characteristics. (The 2011 edition had eight, so a textbook written from it lists a slightly different set.) The nine, with the questions they ask of a system like yours:

CharacteristicThe question it asksSome of its sub-characteristics
Functional suitabilitydoes it do the right things, correctly and completely?completeness, correctness, appropriateness
Performance efficiencyis it fast enough, with the resources available, at the load expected?time behaviour, resource utilisation, capacity
Compatibilitydoes it work beside, and with, other systems?co-existence, interoperability
Interaction capabilitycan its users use it, including users with disabilities?learnability, operability, user error protection, inclusivity
Reliabilitydoes it keep working, and recover when it does not?faultlessness, availability, fault tolerance, recoverability
Securitydoes it protect information and resist attack?confidentiality, integrity, authenticity, accountability, resistance
Maintainabilitycan it be understood, changed and tested?modularity, analysability, modifiability, testability
Flexibilitycan it be moved, installed and adapted?adaptability, scalability, installability
Safetycan it avoid putting people or property in danger?operational constraint, risk identification, fail safe

Functional suitability is the functional requirements themselves, seen as a quality. The other eight are where non-functional requirements come from. Use the table as a checklist: go down it for your project, and for each characteristic either write a requirement or write why none is needed. For a canteen system, safety needs none, and saying so shows the question was asked.

munotes.in63

Non-Functional Requirements

Turning a quality into a requirement

A quality becomes a requirement when you can answer five questions about it:

  1. Which quality? Name the characteristic.
  2. Measured how? Seconds, per cent, taps, pixels, attempts, hours.
  3. To what target? The number that separates pass from fail.
  4. Under what conditions? How many users, on what device, over what network, at what time.
  5. Verified how? Which test, in which chapter of your plan, will prove it.

Watch the difference:

UntestableTestable
The system shall be fastWith 100 students ordering in the same minute on the lab server, 95 per cent of menu and order requests are answered within 1 second
The system shall be securePasswords are stored only as salted scrypt hashes with the cost settings of the OWASP Password Storage Cheat Sheet
The system shall be user-friendlyA one-item order takes no more than 4 taps from the menu page
The system shall be availableThe service restarts itself within 10 seconds after a crash, and starts when the server boots
The system shall work on phonesEvery page is usable at a width of 320 CSS pixels without sideways scrolling

Two details in the first testable row matter. "95 per cent" instead of "all" admits that at a moment of load a few requests will be slower, and sets a limit on how many. "On the lab server" fixes the conditions: the same system is faster on a powerful machine and slower on a weak one, so a performance requirement without its conditions cannot be checked.

Borrow numbers from standards

For some qualities, somebody has already done the hard work of choosing testable numbers, and you should use theirs rather than inventing your own.

Accessibility has the Web Content Accessibility Guidelines, WCAG 2.2, a W3C Recommendation. Its success criteria are precise:

WCAG 2.2 success criterionLevelWhat it requires
1.4.3 Contrast (Minimum)AAtext has a contrast ratio of at least 4.5:1 against its background, 3:1 for large text
1.4.10 ReflowAAcontent works without scrolling in two dimensions at a width equivalent to 320 CSS pixels
2.5.8 Target Size (Minimum)AAa pointer target, such as a button, is at least 24 by 24 CSS pixels, with some exceptions
2.1.1 KeyboardAall functionality can be operated through a keyboard
3.3.2 Labels or InstructionsAlabels or instructions are provided when content requires input

Each of these is a requirement you can quote, with a number, and test. Level A is the minimum; level AA is the level most organisations aim for.

munotes.in64

Non-Functional Requirements

Password storage has the OWASP Password Storage Cheat Sheet, which Chapter 31 uses. Performance has no universal standard number, so your target must come from your own problem, as the worked team's did from its load estimate in Chapter 6.

The worked project: non-functional requirements

The worked team went down the ISO list and wrote twelve requirements. Every one says how it will be verified, and every one was verified; the last column says where.

IDCharacteristicRequirementVerified in
NFR-1performance efficiency: time behaviourwith 100 students ordering in the same minute on the lab server, 95 per cent of menu and order requests are answered within 1 second, and none failsload test, Chapter 63
NFR-2performance efficiency: capacity; reliabilitywhen many students order the last k portions of an item at the same moment, exactly k orders are acceptedconcurrency test, Chapters 45 and 63
NFR-3security: confidentialitypasswords are stored only as salted scrypt hashes with settings from the OWASP Password Storage Cheat Sheet, and are never loggedunit tests and code review, Chapters 46 and 51
NFR-4security: authenticitythe sign-in cookie cannot be read by page scripts, is not sent with another site's form posts, is sent only over HTTPS when the site uses HTTPS, and lasts 8 hoursintegration tests, Chapter 53
NFR-5security: confidentiality, integrityeach role can do only what the SRS allows it, and no student can see or change another student's orderintegration and security tests, Chapters 53 and 65
NFR-6security: resistanceafter 5 failed sign-ins for one email address from one network address within 15 minutes, further attempts are refused for the rest of that windowintegration test, Chapter 53
NFR-7interaction capability: operabilityevery page is usable at 320 CSS pixels wide without sideways scrolling (WCAG 2.2 SC 1.4.10), a one-item order takes no more than 4 taps from the menu page, and every counter action is a single tap, with no typingmeasured in the browser, Chapter 54
NFR-8interaction capability: inclusivityevery input has a visible label; text contrast is at least 4.5:1 (SC 1.4.3); tap targets are at least 24 by 24 CSS pixels (SC 2.5.8); every action works from a keyboard (SC 2.1.1)measured, Chapter 54
NFR-9compatibility; flexibilityworks in current Chrome, Firefox, Safari and Edge, and in the team's Android appsystem testing, Chapters 54 and 59
NFR-10reliability: recoverabilitythe service restarts itself within 10 seconds of a crash, and starts when the server bootsserver tests, Chapter 60
NFR-11maintainability: testability, analysabilitythe whole automated test suite runs with one command, and the README lets a new developer run the system within 30 minutesChapters 50 and 69
NFR-12flexibility: installabilityruns on Windows, macOS and Linux with Node.js 22 or later and MySQL 8.0 or laterinstallation on the team's machines, Chapter 39
munotes.in65

Non-Functional Requirements

Some notes on how they arrived at these.

NFR-1 is the load estimate of Chapter 6 made into a promise. The team estimated a peak of about 7 orders a minute and set the requirement at 100, about fourteen times more, because the estimate might be wrong and the cost of a slow rush is high.

NFR-2 came from a question nobody asked in an interview. Ganesh had said two students sometimes claim the same plate. Farhan asked what happens if they both press Place order for the last portion at the same instant. The answer depends entirely on how the stock is updated, and the requirement makes sure somebody tests it.

NFR-7's 320 pixels is WCAG's number, not the team's. Their first draft said 360, the width of a common phone. Reading WCAG 2.2, they found SC 1.4.10 sets 320, and adopted it: a published number is easier to defend than a guessed one.

The team measured NFR-8 before writing it. They computed the contrast of every text colour in their stylesheet with the formula WCAG gives for relative luminance, and the weakest pair, the red of an error message on white, was 6.47:1, comfortably above 4.5:1. A requirement you already know you meet is a strong one to put in front of an examiner.

Safety was considered and left out, in writing. Nothing the canteen system does can put a person or property in danger, and the SRS says so under safety, so the reader knows the question was asked.

Do this for your project

  1. Go down the nine ISO/IEC 25010 characteristics. For each, write a requirement or a sentence saying why none is needed.
  2. For each requirement answer the five questions: quality, measure, target, conditions, verification.
  3. Take numbers from standards where they exist: WCAG 2.2 for accessibility, the OWASP cheat sheets for security.
  4. Take performance numbers from your own load estimate, with a margin.
  5. Add a "verified in" column and make sure your test plan in Module 2 contains every test it names.
  6. Where you can, measure before you promise.

Mistakes that cost marks

Adjectives instead of numbers. Fast, secure, reliable, user-friendly, scalable: none is testable without a number and a condition.

A target with no conditions. "Within 1 second" on what machine, with how many users?

A non-functional requirement no test ever checks. It is a claim in the SRS the examiner can ask you to demonstrate.

munotes.in66

Non-Functional Requirements

Security as one line. Security is several different requirements about passwords, sessions, access and attack, each tested differently.

Accessibility forgotten. Many examiners now ask about it, and WCAG 2.2 gives you ready-made, testable requirements.

Quick revision

  • A non-functional requirement says how well; it needs a number and a way to verify it.
  • ISO/IEC 25010:2023 has nine characteristics: functional suitability, performance efficiency, compatibility, interaction capability, reliability, security, maintainability, flexibility, safety. The 2011 edition had eight.
  • Five questions: which quality, measured how, to what target, under what conditions, verified how.
  • Borrow numbers: WCAG 2.2 (contrast 4.5:1, reflow at 320 CSS px, targets 24 by 24 CSS px, keyboard, labels); the OWASP cheat sheets for security.
  • Performance targets come from your own load estimate, with a margin.
  • Every requirement has a "verified in" entry that leads to a real test.

Questions you must be able to answer

1. Distinguish functional and non-functional requirements with one example of each from the worked project. A functional requirement states a behaviour: the system shall let a student cancel their own order while it is placed. A non-functional requirement states how well the system behaves: with 100 students ordering in the same minute, 95 per cent of menu and order requests are answered within 1 second.

2. What is ISO/IEC 25010, and how is it used when writing requirements? The international standard that defines a product quality model for software; its 2023 edition has nine characteristics, each with sub-characteristics. It is used as a checklist: for each characteristic the team either writes a measurable requirement or records why none is needed.

3. Rewrite "the system should be user-friendly" as testable requirements. For example: a one-item order takes no more than 4 taps from the menu page; every page is usable at 320 CSS pixels wide without sideways scrolling; every input has a visible label; every counter action is a single tap.

4. Why does NFR-1 say 95 per cent and name the lab server? Because under load a few requests are always slower, so the requirement limits how many may be, and because performance depends on the machine and the load, so a target without its conditions cannot be tested or reproduced.

5. Which WCAG 2.2 success criteria did the worked project adopt, and what does each require? 1.4.3, text contrast of at least 4.5:1; 1.4.10, no two-dimensional scrolling at 320 CSS pixels wide; 2.5.8, pointer targets of at least 24 by 24 CSS pixels; 2.1.1, all functions operable from a keyboard; and labels for every input, as 3.3.2 requires.

Contents This chapter on its own page

munotes.in67

Chapter Twelve

Use-Case Analysis

Syllabus topic Module 1, "Requirement Engineering: ... Use-case analysis".

In one line

Use-case analysis describes, one goal at a time, how each kind of user interacts with the system to get something of value done, including everything that can go wrong along the way; each written use case becomes a diagram, a design and a set of tests.

In the wording to use when asked: use-case analysis identifies the actors of a system and the goals they pursue through it, and specifies each goal as a use case: a sequence of interactions between actor and system, with its preconditions, main success scenario, extensions and postconditions, that yields an observable result of value to the actor.

Why tell the requirements as stories

A list of functional requirements says what the system must do, one behaviour at a time. It does not show how those behaviours fit together from the user's side: what the student does first, what the system answers, what happens next, and where it can go wrong. A use case tells exactly that, as a short, numbered story with one person trying to achieve one thing.

That shape catches mistakes a list hides. Writing out "the student places an order" step by step forces the questions "what if the slot has closed while she was choosing?" and "what if another student took the last portion a second before her?". Each such question is a requirement you would otherwise have missed, and each answer is a test you will later run.

The words

Actor. UML 2.5.1 describes an actor as a role played by something outside the system that interacts with it: a human user, a piece of hardware or another system. An actor is a role, not a person. Lata Pawar is the owner, but when she marks an order collected she is acting in the counter staff role.

Use case. A use case specifies what the system does with an actor to reach one goal. In the specification's words, it "yields an observable result that is of value" to an actor. "Observable" and "of value" are the two tests. "The system checks the stock" is observable to nobody and valuable to nobody by itself; it is a step inside a use case. "Place an order" is a use case: the student ends up with an accepted order and its number.

Primary actor. The actor whose goal the use case serves, who usually starts it.

Scenario. One particular path through a use case: everything goes right, or the slot has closed, or the stock has run out.

Finding the use cases

Start from the actors, not from the screens.

  1. List the actors from your stakeholder register (Chapter 5): the user classes, plus any external system.
  2. For each actor, list their goals: what do they come to the system to get done?
  3. One use case per goal. Name it with a verb and an object from the actor's point of view: "Place an order", "Set today's stock".
  4. Check the level. A good use case is a goal the actor would be satisfied to have achieved in one sitting. "Tap the plus button" is too small; it is a step. "Run the canteen" is too big; it is many goals.
  5. Map each use case to the functional requirements it realises, and check that every functional requirement is realised by some use case.
munotes.in68

Use-Case Analysis

Writing one out

A use case can be written at three levels of detail. A brief use case is one paragraph. A casual one is a few paragraphs of story. A fully written one follows a template, and it is the form to use for the important and complicated goals of your project:

SectionWhat goes in it
ID and nameUC-4 Place an order
Primary actorwho is pursuing the goal
Goalthe goal in one sentence, from the actor's side
Preconditionswhat must already be true before it starts
Triggerwhat starts it
Main success scenariothe numbered steps when everything goes right, alternating actor and system
Extensionseach numbered step's alternatives and failures, numbered after the step: 3a, 3b
Postconditionswhat is true when it ends successfully
Requirementsthe FR and NFR identifiers it realises

The heart is the main success scenario: the steps of the path where nothing goes wrong, written in plain sentences, each saying who does what. Then comes the valuable part, the extensions: for each step, what else could happen, and what the system does then. Numbering an extension after its step, 3a, means "instead of step 3 going as written".

Write the steps as what happens, not how the screen looks. "The student chooses the items and quantities" survives a redesign of the menu page. "The student taps the orange plus button beside the item" does not.

Two relationships between use cases

When use cases share behaviour or add to each other, UML offers two relationships. They are drawn in Chapter 20, but they are decided here.

Include. When the same steps occur in two or more use cases, they can be written once as a use case of their own, which the others include. The included steps always happen as part of the including use case.

Extend. When a use case adds optional behaviour to another at a particular point, under a condition, it extends that other use case. The extended use case makes complete sense without the extension; the extension only makes sense inside it.

munotes.in69

Use-Case Analysis

Use both sparingly. A use case model with arrows everywhere is usually a flowchart in disguise. The worked team found one genuine extension and no genuine inclusion, and said so.

The worked project: the use cases

Actors and goals

The worked team had three human actors, from Chapter 5's user classes: Student, Counter staff and Owner. The owner can do everything the counter staff can, and more; in UML terms the owner is a specialisation of the counter staff actor, which Chapter 20 draws as an arrow with a hollow triangle.

The list

IDUse casePrimary actorRealises
UC-1RegisterStudentFR-1
UC-2Sign in / sign outany userFR-2
UC-3Browse today's menuStudent (or anyone)FR-4
UC-4Place an orderStudentFR-7, FR-8, FR-9, FR-10, FR-11
UC-5View my ordersStudentFR-12
UC-6Cancel an orderStudentFR-13
UC-7View orders by slotCounter staffFR-14
UC-8Update order statusCounter staffFR-15
UC-9View kitchen listCounter staffFR-16
UC-10Manage the menuOwnerFR-5
UC-11Set today's stockOwnerFR-6
UC-12View daily reportOwnerFR-17
UC-13Create staff accountOwnerFR-3

All seventeen functional requirements appear in the last column, and every use case realises at least one. That two-way check is the first thing the team's guide asked about.

UC-4 Place an order, fully written

Primary actor: Student. Goal: to order lunch for a pickup slot, and know the order number. Preconditions: the student is signed in. At least one item can be ordered and at least one slot is open. Trigger: the student opens the menu page.

Main success scenario:

  1. The system shows today's menu, and the pickup slots that are still open with their cut-off times.
  2. The student chooses one or more items and a quantity of each, and a pickup slot.
  3. The student asks the system to place the order.
  4. The system checks that the slot is still open, that the student has no active order for that slot, and that there is enough of every item left.
  5. The system takes each item from the stock, records the order as placed, and gives it a number.
  6. The system shows the student the order number and the slot.

Extensions:

  • 2a. The student chooses more than 5 of one item or more than 10 items in all: the menu page will not let the quantity go higher. If a request arrives anyway, the system refuses it, naming each problem.
  • 3a. The student has lost their connection: the page says it cannot reach the canteen server and the order is not placed. The student tries again.
  • 4a. The slot has closed since the page was opened: the system refuses the order and says ordering for that slot has closed. The student chooses another slot and returns to step 3.
  • 4b. The student already has an active order for this slot: the system refuses, saying so. The student chooses another slot, or keeps the existing order.
  • 4c. An item has been taken off today's menu: the system refuses the order, naming the item.
  • 4d. Fewer portions of an item are left than were asked for: the system refuses the whole order, saying how many are left, or that the item is sold out, and nothing is taken from the stock. The student changes the order and returns to step 3.
  • At any step. The student's sign-in has expired: the system refuses the order and asks the student to sign in again.
munotes.in70

Use-Case Analysis

Postconditions (success): the order is recorded as placed for the chosen slot and today's date, the stock of each item is reduced by its quantity, and the student has the number. Requirements: FR-7, FR-8, FR-9, FR-10, FR-11; NFR-1, NFR-2.

Read extension 4d again. It is FR-9's hardest sentence from Chapter 10, told as a story, and it will become a test in Chapter 53 in which a thali is ordered with too much biryani and the thali's stock is checked afterwards.

UC-6 Cancel an order, and the one extension

Primary actor: Student. Goal: to cancel an order the student no longer wants, before the kitchen starts on it. Preconditions: the student is signed in and viewing their orders (UC-5); the order is theirs and is placed.

Main success scenario:

  1. The student asks to cancel the order.
  2. The system asks the student to confirm.
  3. The student confirms.
  4. The system marks the order cancelled and returns its items to the stock.
  5. The system shows the order as cancelled.

Extensions:

  • 3a. The student changes their mind: the order is kept.
  • 4a. The counter has started preparing the order since the page was loaded: the system refuses, saying the order is being prepared and can no longer be cancelled.

Cancelling happens only from the list of one's own orders, and only when an order is still placed. It is optional behaviour added to UC-5 at one point, under one condition. That is exactly what the extend relationship means, and it is the only one in the worked team's model: UC-6 extends UC-5.

They looked for an include and did not find a real one. Signing in is needed before most use cases, and some textbooks draw that as an include. The team treated it as a precondition instead, because signing in is a goal in its own right (UC-2), not a shared fragment of other goals, and wrote that reasoning into the SRS.

munotes.in71

Use-Case Analysis

UC-8 Update order status, briefly

The counter staff member, seeing an order in the list for the current slot, moves it to its next status with a single tap: placed to being prepared, being prepared to ready, ready to collected when the student pays, or ready to not collected at the end of the break. The system refuses any other change, such as placed straight to ready, and says why. An order marked not collected does not return its food to the stock.

The team wrote UC-8 as a brief use case because its rules are already exact in FR-15. A fully written version would have repeated them.

Do this for your project

  1. List the actors from your stakeholder register. Remember external systems, if any.
  2. List each actor's goals, and name one use case per goal, verb first.
  3. Check the level of each: satisfying in one sitting, neither a single click nor a whole job.
  4. Map use cases to functional requirements, both ways.
  5. Write the two or three most important use cases fully, extensions included. Brief versions are enough for the simple ones.
  6. Decide include and extend relationships honestly; prefer a precondition to a doubtful include.

Mistakes that cost marks

Use cases that are screens. "Menu page", "Admin panel" are places, not goals.

Use cases that are steps. "Validate quantity" and "Check stock" have no actor who would call them a goal.

No extensions. A use case with only its happy path has specified the easy half.

Interface details in the steps. A redesign should not make a use case wrong.

Include and extend used as flowchart arrows. They are relationships between goals, not the order in which things happen.

Quick revision

  • An actor is a role outside the system: a human, hardware or another system.
  • A use case is one goal, yielding an observable result of value to an actor.
  • Find them from actors and their goals; one use case per goal; check the level.
  • A fully written use case: ID and name, primary actor, goal, preconditions, trigger, main success scenario, extensions numbered after their steps, postconditions, requirements.
  • Include: shared behaviour always inserted. Extend: optional behaviour added at a point, under a condition.
  • Every functional requirement is realised by some use case, and every use case realises some requirement.

Questions you must be able to answer

1. What is a use case, and how do you tell a use case from a step? A specification of how the system and an actor interact to achieve one of the actor's goals, yielding an observable result of value to that actor. A step, such as checking the stock, is not observable or valuable to the actor on its own; a use case, such as placing an order, ends with a result the actor wanted.

munotes.in72

Use-Case Analysis

2. What is the main success scenario, and what are extensions? The main success scenario is the numbered sequence of steps when everything goes right. Extensions describe what else can happen at particular steps, alternatives and failures, numbered after the step they replace, such as 4a, together with how the system responds.

3. Distinguish include from extend, with an example. An include inserts shared behaviour that always happens as part of the including use case, used when two or more use cases share steps. An extend adds optional behaviour to another use case at a particular point under a condition; in the worked project, cancelling an order extends viewing one's orders, when the order is still placed.

4. Why did the worked team not draw sign-in as an include? Because signing in is a goal in its own right with its own use case, and it happens before the other goals rather than as a shared fragment inside them. It was written as a precondition of the other use cases instead.

5. What does extension 4d of UC-4 require, and how will it be tested? That when any item has too few portions left, the whole order is refused and nothing is taken from the stock. It is tested by ordering a thali together with more biryani than is left, checking the order is refused, and checking that the thali's stock is unchanged.

Contents This chapter on its own page

munotes.in73

Chapter Thirteen

Requirement Prioritization

Syllabus topic Module 1, "Requirement Engineering: ... Requirement prioritization".

In one line

Prioritising requirements means deciding, before building starts, which requirements the project must deliver, which it should, which it could if time allows, and which it will not deliver this time, so that when time runs short the least important work is dropped and the project still succeeds.

In the wording to use when asked: requirement prioritisation ranks requirements by their value to stakeholders, weighed against their cost, risk and dependencies, so that a project with fixed time and resources delivers the most valuable subset first and has planned contingency; MoSCoW classifies each requirement as Must Have, Should Have, Could Have or Won't Have this time.

Why a student project needs it most of all

Your time is fixed: a semester, and 60 hours each. Your team is fixed. The only thing that can give when something goes wrong is how much you build. And something always goes wrong: an illness, an examination in another paper, a bug that takes three evenings.

A project with no priorities meets that moment in the worst way. Everything was equally important, so everything was started, and at the end several things are half-built and none works. A project with priorities drops the least important work, finishes the rest, and demonstrates a complete, working system with a written list of what was deliberately left for later. The second project scores far better, and it is the one an examiner trusts.

The simple ways, and why they are weak

High, medium, low. Easy, and weak: nobody defines what "high" commits you to, and "medium" becomes a place to put everything you cannot decide about.

A strict ranking, 1 to 20. Precise, and exhausting: the team argues for an hour about whether an item is ninth or tenth, and the argument changes nothing.

The Agile Business Consortium, which maintains the DSDM agile framework from which the next technique comes, makes exactly these two objections to them.

MoSCoW

MoSCoW sorts requirements into four groups. The capital letters are the initials; the small o's are only there to make it a word.

Must Have. The requirements the project guarantees. DSDM expands the word MUST as "Minimum Usable SubseT": without these there is no point delivering at all. The test for a Must is a question: what happens if this is not delivered? If the honest answer is "then there is no point using the system", it is a Must. If there is any way round it, even a painful manual one, it is not.

Should Have. Important, but not vital. Painful to leave out, and the system is still usable without it, perhaps with a workaround.

Could Have. Wanted, but less important. These are the project's contingency: the first things dropped when time runs short, and the reason the Musts can be protected.

munotes.in74

Requirement Prioritization

Won't Have this time. Requirements everyone has agreed will not be delivered in this release. They are written down all the same, so that they are not quietly slipped back in later, and so that nobody is surprised by their absence.

Two points about MoSCoW are often missed.

It is about effort, not only about counting. DSDM recommends that the Must Haves take no more than about 60 per cent of the effort of a project, and that around 20 per cent of the effort be Could Haves. A project whose Musts take 90 per cent of the time has no room for anything to go wrong.

It applies to a timeframe. "Won't have this time" means not in this release, not never. A requirement can be a Must for the project as a whole and a Should for its first increment.

Who decides

Value is decided by the person who owns the problem, here the owner of the canteen. Effort and risk are estimated by the team. A priority is agreed between them, and it must respect dependencies: if a Must cannot work without a Should, that Should is really a Must.

Two other techniques worth knowing

Value against effort. Draw a two-by-two grid: value to the stakeholders up the side, effort to build along the bottom. High value and low effort are quick wins, to do first. High value and high effort are major pieces, to plan carefully. Low value and low effort fill spare time. Low value and high effort are not worth doing.

Hundred-point allocation. Give each stakeholder 100 points to spread across the requirements however they like. The totals rank the requirements by how much people actually care, and they reveal disagreements between stakeholders that a meeting might smooth over.

Both are useful for sorting within a MoSCoW group, or for settling an argument about one.

The worked project: prioritising

Estimating the effort

Before they could check the 60 per cent guidance, the worked team needed an effort for each requirement. Farhan and Rohan estimated each one in person-hours to build and test, from their experience of the Express projects of Semester 4:

RequirementEffort (hours)
FR-1 Register4
FR-2 Sign in and sign out6
FR-3 Counter staff accounts2
FR-4 Today's menu4
FR-5 Menu items4
FR-6 Today's stock2
FR-7 Place an order8
FR-8 Pickup slots3
FR-9 Stock6
FR-10 One order per slot2
FR-11 Order number1
FR-12 My orders4
FR-13 Cancel an order3
FR-14 The counter's list4
FR-15 Moving an order on4
FR-16 The kitchen list3
FR-17 Daily report4
Total64
munotes.in75

Requirement Prioritization

The first attempt, and what was wrong with it

The team sat with the owner, asked the Must question of each requirement, and produced this:

PriorityRequirementsEffort (hours)
Must HaveFR-1, 2, 4, 6, 7, 8, 9, 11, 12, 14, 1546
Should HaveFR-3, 5, 10, 13, 16, 1718
Total64

Each Must passed the test. Without registration (FR-1), the owner would have to create two thousand accounts. Without the stock (FR-6 and FR-9), the system would promise food that did not exist. Without the counter's list and its status changes (FR-14 and FR-15), the counter could not serve anyone. Each Should had a workaround: staff accounts could be created once by the team, the menu could be entered by the team, and the owner could count sales from slips as she does now.

But 46 of 64 hours is 71.9 per cent Must Have, and there were no Could Haves at all. Their guide, Prof. Iyer, pointed out what that meant: every hour of the plan was committed, and the first thing to go wrong would eat into a Must.

The second attempt

The team had in fact wanted three more features, and had never written them down because they were "extras". They added them, as Could Haves, with estimates:

Could HaveWhy they wanted itEffort (hours)
A browser notification to the student when the order is readystudents would not need to keep the page open6
Orders from earlier days on the My orders pagestudents asked for it in the group interview4
A student's favourite items shown firstquicker ordering for regulars4
Total14

Now the whole picture:

PriorityEffort (hours)Share of effort
Must Have4659.0 per cent
Should Have1823.1 per cent
Could Have1417.9 per cent
Total78100.0 per cent

The Musts are now 59.0 per cent of the effort, just under DSDM's 60, and the Coulds give about 18 per cent of contingency. Nothing about the Musts changed. What changed is that the plan now had room to absorb trouble, and the room was written down.

Won't Have this time

The out-of-scope list of Chapter 4 became the Won't Haves, each with its reason: online payment, SMS alerts, a penalty for students who do not collect, delivery to classrooms, more than one canteen, and the stock of raw materials.

What happened, and the first release

The first release, the version that must exist for the canteen trial, is the Must Haves: 46 hours. The Should Haves were planned to follow within the same semester.

In the event, all the Musts and all the Shoulds were built. Of the Coulds, none was: the time went instead on making the stock rule safe when many students order at once (Chapter 45), which took longer than its 6-hour estimate. That is exactly what the Coulds were for, and the final report lists all three as the first items for a future release.

munotes.in76

Requirement Prioritization

Prioritising non-functional requirements

Non-functional requirements are usually floors rather than features: a password is stored safely or it is not. Most are therefore Must Haves, and they are prioritised by checking that each is really needed at the level stated. An accessibility target is not dropped when time is short; a performance target of 100 students a minute might be relaxed to 50 if the evidence says 7 is the real peak, but only by a recorded decision.

Do this for your project

  1. Estimate the effort of each requirement in hours, to build and to test.
  2. With the owner of the problem, ask of each: what happens if this is not delivered? Sort into Must, Should, Could and Won't Have this time.
  3. Check dependencies: a Should that a Must needs becomes a Must.
  4. Add up the effort. If the Musts are well over 60 per cent, or there are no Coulds, you have no contingency: reconsider.
  5. Write down the Won't Haves with their reasons.
  6. Mark the first release: the Musts.
  7. Keep the list. Every change of priority later gets a line in the version history.

Mistakes that cost marks

Everything is a Must. It means nothing was prioritised, and the plan has no contingency.

No Won't Haves. The scope has no written edge.

Priorities set by the team alone. Value belongs to the owner of the problem; a team that decides it alone is guessing.

Priorities without effort. Without hours, the 60 per cent check cannot be made.

Coulds quietly built first, because they are fun, while a Must waits. It happens, and it is visible in the Git history.

Quick revision

  • Prioritise because time and people are fixed; scope is what flexes.
  • MoSCoW: Must Have, Should Have, Could Have, Won't Have this time.
  • Must = the "Minimum Usable SubseT"; the test is "what happens if this is not delivered?"
  • DSDM's guidance: Musts at most about 60 per cent of effort, Coulds about 20 per cent as contingency.
  • Value is the owner's to decide; effort and risk are the team's to estimate; dependencies can promote a Should.
  • Other techniques: value against effort, hundred-point allocation.
  • The first release is the Musts.

Questions you must be able to answer

1. What does MoSCoW stand for, and how do you decide whether a requirement is a Must? Must Have, Should Have, Could Have and Won't Have this time. A requirement is a Must when the answer to "what happens if it is not delivered?" is that there would be no point using the system; if any workaround exists, it is a Should or a Could.

munotes.in77

Requirement Prioritization

2. Why is a project with 90 per cent of its effort in Must Haves at risk? Because there is no contingency. When anything goes wrong, the only work left to drop is a Must, so the project fails its own minimum. DSDM recommends keeping Must effort to about 60 per cent and around 20 per cent in Could Haves.

3. Why write down Won't Haves at all? So that the scope has a clear edge, stakeholders are not surprised by what is missing, and the items are not quietly reintroduced later. "This time" also records that they may come in a later release.

4. Show the worked team's corrected effort split. Must Haves 46 hours, Should Haves 18 and Could Haves 14, a total of 78. That is 59.0 per cent Must, 23.1 per cent Should and 17.9 per cent Could, against the first attempt's 71.9 per cent Must with no Could Haves at all.

5. What happened to the worked team's Could Haves, and was that a failure? None was built, because the time went on making concurrent ordering safe, which took longer than estimated. That is not a failure: Could Haves exist precisely to be dropped so that Musts and Shoulds are finished, and they are recorded as the first items of a future release.

Contents This chapter on its own page

munotes.in78

Chapter Fourteen

Constraints and Assumptions

Syllabus topic Module 1, "Requirement Engineering: ... Constraints and assumptions".

In one line

A constraint is a limit the project must work within and cannot change, and an assumption is something the project takes to be true without having proved it; both are written down because constraints explain the design, and every assumption is a risk that has not happened yet.

In the wording to use when asked: constraints are restrictions imposed on the project or the system, by time, budget, technology, law, standards or the organisation, that limit the design options; assumptions are conditions believed to hold, on which the requirements or the plan depend, and which must be recorded, owned and verified, because if one proves false the requirements or the plan may fail.

Why they are written down

Every design is shaped by things the designers could not choose. Without a list of them, a reader of your SRS sees decisions that look arbitrary. "Why can students not pay in the app?" has an answer, and the answer is a constraint: the project cannot obtain a payment gateway account. Once it is written down, the decision explains itself.

Assumptions matter for a different reason. Every project rests on things nobody has checked: that users have phones, that the network works at lunchtime, that somebody will enter the data each morning. Most of them are true. The ones that are not become failures, usually at the worst moment. A written assumption can be checked, and an assumption that cannot be checked can at least be watched.

Constraints

A constraint limits how the system may be built or run. It is not a requirement the stakeholders want; it is a boundary the project must stay inside, set by someone or something outside the team.

KindWhere it comes fromExample
Timethe semester, MU's hours60 hours per student
Moneywhoever paysno budget for software or hosting
Technologythe organisation's machines, the team's skillsmust run on the college's machine and network
LawActs and ruleshow personal data may be stored
StandardsMU, the organisation, an industrythe code must be kept on GitHub
The organisationits policies and approvalsonly college email addresses may register
Interfacesother systems it must work withnone, in the worked project

A good test: could the team change it by deciding differently? If yes, it is a design decision, not a constraint. "We will use MySQL" is a decision. "The college will only let us use its own machine" is a constraint.

Record each with its source, because a constraint without a source looks like an excuse.

Assumptions

An assumption is something the project believes to be true and depends on, but has not proved, or cannot control. Assumptions come from everywhere: about users ("they have phones"), about the environment ("the Wi-Fi reaches the canteen"), about other people's behaviour ("the owner will enter the stock"), about the future ("the break times will not change").

munotes.in79

Constraints and Assumptions

Recording an assumption properly means answering four questions about it:

  1. Why do we believe it? The evidence, if any.
  2. What happens if it is false? The consequence for the system and its users.
  3. How and when will we check it? A test, a question to someone, an observation during a trial.
  4. Who owns it? One team member responsible for checking it.

An assumption with no evidence and a severe consequence is not really an assumption. It is a risk, and it belongs on your risk list with a mitigation (Chapter 6).

Dependencies

A close relative is the dependency: something the project needs from someone outside the team. The college's machine, the IT in-charge's time to set it up, the owner's agreement to a trial. List them beside the assumptions; each is an assumption that another person will do something.

When an assumption turns out false

It happens, and what matters is what you do next:

  1. Record it: which assumption, when it was found false, how.
  2. Assess the impact: which requirements, designs or plans relied on it.
  3. Decide: change the design, change the requirement, add a workaround, or accept the limitation, and say who decided.
  4. Update the documents, with a version note, so the SRS stops asserting something untrue.

The worked project: constraints

IDConstraintSource
C-1The work must fit 60 hours per student over the semesterMU: 2 credits, 30 hours each
C-2No money for software or hosting; everything used must be freethe team; the college
C-3The system runs on the college's machine and network, so ordering needs the college Wi-Fithe IT lab in-charge (Chapter 6)
C-4The code is kept in a GitHub repositoryMU: "Version control using GitHub (Mandatory)"
C-5No online payment in this releaseno payment gateway account is obtainable (Chapter 4)
C-6Personal data is limited to a name, a college email address, a password hash and ordersthe law on personal data (Chapter 8); the principal's condition
C-7Built with Node.js, Express, MySQL, HTML, CSS, JavaScript and Kotlinthe team's skills (Chapter 6)

C-7 needs a comment, because by the test above it looks like a decision. The team could have chosen PHP. What made it a constraint is the other half: the team could not have chosen a technology none of them knew and still finished in 60 hours each. They recorded the skills limit as the constraint and the stack as the decision it led to.

munotes.in80

Constraints and Assumptions

The worked project: assumptions

IDAssumptionWhy believedIf falseChecked byOwner
A-1Students have a phone with a browser and can join the college Wi-Fi176 of 180 surveyed have a smartphone with internet; every student has a Wi-Fi loginsome cannot orderthe trial: count walk-ins who say they could not orderAditi
A-2The owner enters each day's pre-order stock before 11:00she agreed tosee belowa morning checklist; the first week of the trialFarhan
A-3Every student has a college email address and can read itthe principal's officesome cannot registerthe principal's office confirmed in writingAditi
A-4The counter has a tablet or laptop on the college Wi-Fi during the breakthe owner will use her phone until the tablet is boughtthe counter cannot see ordersset up before the trialRohan
A-5The lunch break is 12:30 to 13:10 on every college daythe college timetableslots at the wrong timeschecked against each term's timetableSneha
A-6Students pay the right amount at the counterhow the counter already worksdisputes at the counterthe order total is shown on the counter's listGanesh, with the team

A-2 is a risk, and the team said so

Consider A-2 closely, because it shows why assumptions must be taken seriously.

The system keeps one number per item: the portions left for pre-order. Orders reduce it; cancellations raise it; the owner sets it each morning. Nothing in the first release resets it overnight. So if the owner forgets one morning, the system starts the day with whatever was left the evening before: perhaps 12 portions of Veg Biryani that the kitchen has no plan to cook.

The team found this while writing the table above, when they asked "what if it is false?" of A-2. They had three options: reset every item to zero at midnight, which would make the menu look sold out every morning until the owner acted; record the date with each stock figure and treat an old figure as zero, which needed a change to the database design; or keep the design, and guard the assumption. With the design already under review, they chose the third for the first release: the owner's morning routine in the user manual (Chapter 67) begins with setting the stock, and the counter staff check it at 11:00. The limitation, and the date-stamped stock as its proper fix, are written into the SRS and into the final report's future work.

That is an honest outcome. An examiner who asks "what happens if the owner forgets?" gets an answer that shows the team thought about it, knows the weakness, and knows the fix.

munotes.in81

Constraints and Assumptions

A-5, and the price of hard-coding

A-5 exposed a smaller design decision. The pickup slots, 12:30 to 13:00, are written into the application's code as a fixed list. If the college changes its timetable, a developer must change the code. The team kept it that way, because the break had been the same for years and a settings page would have cost hours of a tight plan, and they recorded the consequence beside the assumption. Chapter 43 shows exactly where that list lives.

Do this for your project

  1. List your constraints under the seven kinds, each with its source. Apply the test: could we change it by deciding differently?
  2. List your assumptions: about users, the environment, other people's actions, the future.
  3. For each assumption answer: why we believe it, what if it is false, how and when we check it, who owns it.
  4. Move any assumption with no evidence and a severe consequence to the risk list.
  5. Ask "what if it is false?" of each one against your own design. Expect to find at least one weakness; record what you decide.
  6. List your dependencies on people outside the team.

Mistakes that cost marks

Constraints with no source. They read as excuses.

Decisions disguised as constraints. "We must use MySQL" invites the question "says who?".

Assumptions nobody checks. An assumption without a check and an owner is a hope.

Hiding the weakness an assumption reveals. Examiners ask "what if?" questions; a recorded limitation with a known fix is a good answer, and silence is not.

Quick revision

  • Constraint: a limit set from outside the team (time, money, technology, law, standards, the organisation, interfaces). Test: could we change it by deciding? If yes, it is a decision.
  • Assumption: believed true, not proved, relied upon. Record why believed, what if false, how checked, who owns it.
  • An assumption with no evidence and a severe consequence is a risk.
  • Dependency: something the project needs from someone outside the team.
  • When one proves false: record, assess, decide, update the documents.

Questions you must be able to answer

1. Distinguish a constraint from an assumption, with an example of each. A constraint is a limit the project must work within, set from outside, such as the requirement that the code be kept on GitHub. An assumption is something taken to be true without proof, on which the project relies, such as that the owner will enter each day's stock before 11:00.

2. How can you tell a constraint from a design decision? Ask whether the team could change it by deciding differently. If it could, it is a decision; choosing MySQL was a decision. If it could not, because someone outside the team imposes it, it is a constraint, such as the college allowing only its own machine.

munotes.in82

Constraints and Assumptions

3. What four things should be recorded for each assumption? The evidence for believing it, the consequence if it is false, how and when it will be checked, and who owns the checking.

4. What weakness did the worked team find by examining assumption A-2, and how did they handle it? That the stock figures are never reset overnight, so if the owner forgets to set the day's stock, yesterday's leftover numbers are offered to students. For the first release they guarded the assumption with the owner's morning routine and an 11:00 check by the counter, and recorded the limitation and its proper fix, date-stamped stock, in the SRS and the final report.

5. What should a team do when an assumption proves false during the project? Record when and how it was found false, assess which requirements, designs or plans depended on it, decide on a change, a workaround or an accepted limitation with the name of whoever decided, and update the documents with a version note.

Contents This chapter on its own page

munotes.in83

Chapter Fifteen

Selecting an SDLC Model for a Mini Project

Syllabus topic Module 1, "Software Development Life Cycle (SDLC) Planning: Selection of SDLC model".

In one line

An SDLC model is the overall shape of the work, the order in which a project moves through requirements, design, building, testing and delivery and how often it goes round; selecting one for a mini project means choosing, with reasons, the shape that fits your fixed semester, your changing requirements and MU's fixed deliverables.

In the wording to use when asked: a software development life cycle model defines the phases of software development and the sequence, overlap and iteration between them; selecting an SDLC model involves evaluating candidate models against project characteristics such as requirement stability, schedule constraints, risk, team size, customer availability and documentation obligations, and justifying the choice.

What an SDLC model decides

Every project passes through the same activities: working out what is needed, deciding how to build it, building it, testing it, and putting it into use. An SDLC model decides three things about those activities:

  1. In what order they happen.
  2. How much they overlap: whether design finishes before building starts, or both go on together.
  3. How many times the project goes round: once, from start to finish, or in repeated cycles, each delivering a little more.

The choice matters because it decides when you find out you were wrong. A model that goes round once finds out at the end. A model that goes round many times finds out after each cycle, while there is still time to act.

The models, recalled for this decision

You met these in Software Engineering in Semester 4. This section recalls each only as far as the choice needs.

ModelIts shapeSuits a project whereRisk for a mini project
Waterfalleach phase finishes, and is signed off, before the next beginsrequirements are clear and stable from the startthe system is first seen working at the end, when it is too late to fix misunderstandings
Incrementalthe system is built in pieces, each piece a working part of the whole, added one after anotherrequirements are mostly known and the most important parts can be delivered firstthe design must allow for later pieces
Prototypinga rough version is built quickly to find out what users want, then set aside or reworkedusers cannot say what they want until they see somethingthe prototype is mistaken for the finished product
Spiralrepeated cycles, each beginning with an analysis of the biggest remaining riskthe project is large and riskytoo heavy for a small team in one semester
RADrapid application development: very short cycles, heavy user involvement, reuse of componentsusers are available constantly and the scope is smalldepends on user time a canteen owner does not have
Agile, for example Scrum or XPshort fixed cycles, each ending in working software reviewed with the customer; plans adapt as learning arrivesrequirements will change, and the customer can review oftenlittle written documentation, where MU demands documents
munotes.in84

Selecting an SDLC Model for a Mini Project

Agile in one paragraph

The Manifesto for Agile Software Development, written in 2001, sets out four values. Its authors prefer individuals and interactions to processes and tools, working software to comprehensive documentation, customer collaboration to contract negotiation, and responding to change to following a plan. It adds, in its own words, "while there is value in the items on the right, we value the items on the left more". The manifesto does not reject documentation or plans; it ranks them below working software and change.

Scrum in one paragraph

Scrum is an agile framework, defined by its authors in the Scrum Guide. The Scrum Guide of 2020 describes a Scrum Team of one Product Owner, one Scrum Master and the Developers, typically ten or fewer people. Work happens in Sprints, fixed-length cycles of one month or less. Each Sprint begins with Sprint Planning, has a Daily Scrum of fifteen minutes for the Developers, and ends with a Sprint Review, to inspect what was produced and decide what next, and a Sprint Retrospective, to improve how the team works. The work waiting to be done is the Product Backlog, an ordered list that changes as the team learns.

How to choose

Ask these questions of your own project. Each answer points towards some models and away from others.

QuestionIf the answer is...It points to
Are the requirements clear and stable?yeswaterfall or incremental
no, they will change as users see the systemprototyping or agile
Is the deadline fixed?yes, the semester ends when it endsincremental or agile, where scope flexes and the date does not
How often can the customer review work?every week or twoagile or incremental
rarelymore planning up front
How large and risky is it?small, and the risks are knownnot spiral
Must documents be delivered at fixed points?yes: MU's four Module 1 documentsa planned design phase before building
How experienced is the team?new to the technologyshort cycles, so problems surface early

Notice the tension in a Mini Project. MU wants four design documents at the end of Module 1, which is a waterfall-shaped demand: design before build. But the requirements will certainly change once users see the system, and the semester's end is fixed, which are agile-shaped facts. Pure waterfall ignores the second; pure Scrum ignores the first.

Most mini projects are best served by a hybrid: a planned design phase that produces MU's documents, followed by incremental building in short, fixed cycles, each ending in a working version shown to the users and the guide. That is not a failure to choose. It is the honest answer to the question, and it is exactly what the worked team chose.

munotes.in85

Selecting an SDLC Model for a Mini Project

The worked project: the choice and its reasons

The worked team wrote this in its project proposal:

We will use an incremental model in two stages. In the design stage (27 July to 10 September), we will complete the requirements, the UML set and the architecture, because MU requires these documents at the end of Module 1 and because our requirements come from stakeholders we can meet only a few times. In the build stage (from 11 September), we will build the system in two increments of two weeks, each ending in a working version that we show to the canteen owner and to our guide, followed by testing, deployment and documentation. We chose this over a waterfall model because the owner and the counter staff will understand what they need only when they use a working system, and over pure Scrum because the design documents are required at a fixed date and our stakeholders cannot attend frequent meetings.

And the increments, planned from the priorities of Chapter 13:

StageDatesWhat it deliversShown to
Design27 Jul to 10 Sepproposal, SRS, UML set, architecture documentguide, at the design review on 11 Sep
Increment 111 Sep to 24 Sepordering end to end: the menu, placing an order with the stock rule, the counter's list and status changesowner and counter staff; guide
Increment 225 Sep to 9 Octaccounts, validation and error handling, cancelling, the other Should Haves, deployment to the lab machine and the APKowner, counter staff and head cook; guide
Finishing6 Oct to 26 Octintegration, system, load and security testing; the report, the user manual and the presentationguide

Increment 1 contains Must Haves, because if everything after it went wrong, it alone would still let the canteen take pre-orders. That is the practical meaning of delivering the most important part first. The team made one exception on purpose: FR-10, a Should, is checked inside the same database transaction as the stock rule, two statements in code that was being written anyway, so it was built with it (Chapter 45).

Borrowing from Scrum without pretending to be Scrum

The team took four Scrum practices that fit a student team, and said openly that they were not running Scrum:

  • A backlog: the prioritised requirements list of Chapter 13, kept as issues in the team's GitHub repository (Chapter 56).
  • A short stand-up, fifteen minutes after the Tuesday lecture each week instead of every day, because they are students with other papers: what each did, what each will do, what is blocking them.
  • A review at the end of each increment, with the owner at the canteen after the lunch break.
  • A retrospective at the same meeting: what went well, what to change.
munotes.in86

Selecting an SDLC Model for a Mini Project

They did not have a Scrum Master or a Product Owner in the Scrum Guide's sense. Aditi acted as the owner's representative within the team, because the owner could meet only at the end of each increment.

What the choice bought them

At the first increment review, on 24 September, Ganesh tried the counter screen during a real lunch break. New orders appeared on it only when the page was reloaded, and during the rush he never had a free hand to reload it. The team made the counter's list refresh itself every ten seconds in Increment 2, before anything else had been built on top of the counter screen, and recorded the change in the SRS. In a waterfall project, he would have seen the screen for the first time in late October.

Do this for your project

  1. Answer the six questions in the table for your own project.
  2. Choose a model; most mini projects will choose a design phase followed by short increments.
  3. Write the choice and its reasons in two or three sentences, naming the models you rejected and why.
  4. Plan the increments from your priorities: the first increment should be Must Haves only and should work on its own.
  5. Decide which practices you will use: backlog, stand-up, review, retrospective. Put the meetings in your calendar.
  6. Put the choice in your proposal (Chapter 33) and your plan (Chapter 17).

Mistakes that cost marks

"We used waterfall" with no reasons. The choice itself is less important than the reasoning; the guide will ask why.

Claiming Scrum without its parts. Saying "agile" while holding no reviews and keeping no backlog is easily exposed in a viva.

Increments that are layers. "Increment 1: the database; increment 2: the backend; increment 3: the frontend" delivers nothing usable until the end, which is a waterfall with extra steps. An increment is a working slice of the whole system.

No review with users. An increment nobody outside the team sees has lost its main benefit.

Quick revision

  • An SDLC model decides the order, the overlap and the number of cycles of the development activities.
  • Waterfall (once through, sequential); incremental (working pieces added in turn); prototyping (build to learn); spiral (risk-driven cycles); RAD (rapid, user-intensive); agile (short fixed cycles, working software, adapting plans).
  • Scrum: Product Owner, Scrum Master, Developers (typically 10 or fewer); Sprints of one month or less; Sprint Planning, Daily Scrum of 15 minutes, Sprint Review, Sprint Retrospective; Product Backlog.
  • For a mini project: a design phase for MU's documents, then short increments of working software, the first containing only Musts.
  • Choose by: requirement stability, fixed deadline, customer availability, size and risk, document obligations, team experience.
munotes.in87

Selecting an SDLC Model for a Mini Project

Questions you must be able to answer

1. What does an SDLC model decide? The order in which the development activities happen, how much they overlap, and how many times the project cycles through them: once from start to finish, or repeatedly, delivering more each time.

2. Why is pure waterfall risky for a mini project? Because the users first see the working system at the end, when there is no time left to correct misunderstandings, and requirements for a real problem almost always change once users see something working.

3. Why did the worked team not use pure Scrum? Because MU requires the proposal, SRS, UML set and architecture document at a fixed point at the end of Module 1, and because the canteen owner could not attend frequent reviews. They used a design phase followed by two-week increments, borrowing a backlog, stand-ups, reviews and retrospectives.

4. What is an increment, and why should the first contain only Must Haves? A working slice of the whole system that can be used and reviewed. The first contains only Must Haves so that, if everything later went wrong, the system would still do what is essential.

5. Give one thing the worked team's choice of model allowed them to find early. At the first increment review, the counter staff member found that new orders appeared only when the page was reloaded, which he had no free hand to do during the rush. The list was made to refresh itself every ten seconds in the second increment, before other work depended on the counter screen.

Contents This chapter on its own page

munotes.in88

Chapter Sixteen

The Work Breakdown Structure

Syllabus topic Module 1, "Software Development Life Cycle (SDLC) Planning: ... Work Breakdown Structure (WBS)".

In one line

A work breakdown structure divides the whole of a project's work into smaller and smaller pieces, from the project at the top to work packages at the bottom, each small enough to estimate, give to one person and check when it is done; together the pieces contain all the work and nothing but the work.

In the wording to use when asked: a work breakdown structure is a deliverable-oriented, hierarchical decomposition of the total scope of a project into manageable components, whose lowest level, the work package, is the unit that is estimated, scheduled, assigned and tracked; the WBS as a whole must represent exactly the full scope of the project.

Why break the work down

"Build a canteen ordering system in a semester" cannot be estimated, cannot be given to a person, and cannot be ticked off. "Write the SRS" nearly can. "Draw the use case diagram" certainly can. Breaking work down until each piece can be estimated and owned is the step that turns a project into a plan.

Almost everything else in planning is built on the WBS:

  • estimates are made per work package and added up;
  • the schedule (Chapter 17) arranges the work packages in time;
  • the resource plan (Chapter 18) shares each work package's hours among the people who do them;
  • progress is measured by counting work packages finished;
  • risks are found by asking what could go wrong with each.

The parts of a WBS

The top is the project itself.

The levels below are its major deliverables: the things the project produces. For a mini project these follow MU's own list closely: the documents, the application, the tests, the deployment, the report.

The bottom level is the work package: a piece of work small enough to estimate with confidence, to give to one person, and to recognise as finished. Work packages are where estimates and owners live.

Each element carries a number that shows where it sits: 2 is a deliverable, 2.3 is a part of it, and 2.3.1 would be a part of that. The numbers never change once used, so they can be referred to in the schedule, the resource plan and the minutes of meetings.

The rules that make a WBS correct

The 100 per cent rule. The WBS contains all of the work in the project's scope, and nothing outside it. At every level, the parts of an element add up to exactly that element: no work missing, no work counted twice, and no work that is not in the scope. Project managers call this the 100 per cent rule, and it is the check that finds forgotten work. Students who draw a WBS from memory almost always forget the reviews, the testing and the deployment, and the 100 per cent rule is how you catch it.

munotes.in89

The Work Breakdown Structure

Deliverables, not activities, at the upper levels. Name the upper elements with nouns: "SRS", "Application", "Deployment". A WBS is a breakdown of what is produced, not a list of how it is produced; the how comes in the schedule. Work packages may be phrased as things to produce, "the use case diagram", or as outcomes, "unit tests passing".

Mutually exclusive. A piece of work appears in exactly one place. If the database schema appears under both Design and Application, it will be estimated twice or, worse, done twice.

Small enough, and no smaller. A work package should be small enough that one person can estimate it with confidence and finish it without losing track. Many project managers keep work packages between about a day and two weeks of effort. A student team working a few hours a week scales that down: a work package of between two and twenty hours is about right. Anything bigger hides uncertainty; anything smaller turns the plan into a to-do list.

One owner each. Every work package has one person answerable for it. Others may help.

The WBS dictionary

A diagram shows the structure but not the detail. The WBS dictionary is a table that says, for each work package, what exactly it is:

FieldWhat goes in it
Number and name2.3 SRS
Descriptionwhat the work consists of
Deliverablewhat exists when it is done
Done whenhow anyone can tell it is finished
Ownerone person
Estimatehours of effort
Depends onwhat must be finished first

The "done when" field is the most useful and the most often left empty. "SRS written" is not a criterion; "SRS v1.0 reviewed with the owner, changes made, and approved by the guide" is.

Three ways to draw it

A top-down chart, the classic picture: the project in a box at the top, deliverables in a row beneath it, work packages hanging below each. Clear on paper, and very wide.

A sideways tree: the project on the left, deliverables in a column to its right, work packages to their right. It shows the same structure and stays narrow however many deliverables there are, which suits a report and a phone screen alike.

An outline, numbered and indented, usually as a table with the estimates beside each work package. The least pretty, and the easiest to add up and to keep up to date.

A good WBS document has a picture and an outline: the picture to understand it, the outline to use it.

The worked project: the WBS

The worked team drew its WBS as a sideways tree, in PlantUML, and kept the source in its repository so that anyone could change it and draw it again:

munotes.in90

The Work Breakdown Structure

A sideways tree: Canteen Pre-order on the left, seven deliverables in a column, and each deliverable's work packages to its right

Figure 16.1 The worked team's work breakdown structure: seven deliverables and twenty-eight work packages

This is the source of that figure:

@startmindmap wbs
* Canteen\nPre-order
** 1 Management
*** 1.1 Proposal
*** 1.2 Plan
*** 1.3 Reviews
** 2 Requirements
*** 2.1 Survey
*** 2.2 Interviews
*** 2.3 SRS
** 3 Design
*** 3.1 UML set
*** 3.2 Schema
*** 3.3 API
*** 3.4 Wireframes
*** 3.5 Architecture
** 4 Application
*** 4.1 Frontend
*** 4.2 Backend
*** 4.3 Database
*** 4.4 Security
*** 4.5 Errors
*** 4.6 Reviews
** 5 Testing
*** 5.1 Unit
*** 5.2 Integration
*** 5.3 System
*** 5.4 Load, security
** 6 Deployment
*** 6.1 Local
*** 6.2 Server
*** 6.3 APK
*** 6.4 GitHub
** 7 Documents
*** 7.1 Report
*** 7.2 Manual
*** 7.3 Presentation
@endmindmap

The source uses PlantUML's mind map form, whose branches grow sideways. PlantUML also has a form made for WBS charts, which starts with @startwbs and draws the classic top-down chart; the team tried it first, and with seven deliverables it came out nearly three times as wide as it is tall, too wide for their report's pages.

The outline, with estimates

Hours are person-hours of effort. Module 1's deliverables come first, then Module 2's, each module totalling the 120 hours that four students at MU's 30 hours each have for it.

NumberWork packageOwnerHours
1.1Problem choice and project proposalAditi8
1.2Plan: WBS, schedule, resourcesAditi8
1.3Reviews with the guide, and stand-upsAditi12
2.1Observation and surveySneha10
2.2Stakeholder interviewsAditi8
2.3SRSAditi20
3.1UML setFarhan18
3.2Database schemaFarhan10
3.3API specificationFarhan8
3.4WireframesSneha6
3.5Architecture design documentFarhan12
TotalModule 1120
4.1Frontend pagesSneha18
4.2Backend routes and servicesFarhan16
4.3Database integrationFarhan10
4.4Authentication and validationRohan12
4.5Error handlingFarhan3
4.6Reviews and stand-upsRohan12
5.1Unit testsRohan8
5.2Integration testsRohan5
5.3System and acceptance testingAditi5
5.4Load and security testingRohan4
6.1Local hostingRohan2
6.2Linux serverRohan4
6.3APKRohan4
6.4GitHub repository and releaseAditi2
7.1Technical reportAditi8
7.2User manual and screenshotsSneha3
7.3Presentation and demonstrationAditi4
TotalModule 2120

The two modules together are 240 hours, which is exactly four students at 60 hours each: the 100 per cent rule applied to time. If the work packages had added up to 300, the plan would have been promising work nobody had hours for.

munotes.in91

The Work Breakdown Structure

Meetings are work

The team's first outline had no hours for its own stand-ups, which Chapter 15 set at fifteen minutes every Tuesday. That is seven meetings in Module 1 and six in Module 2, and four people at each. With the reviews held with the guide and the owner, meetings come to 12 hours in each module, a tenth of its time. Time spent is part of the work whether or not anyone plans it, so the 100 per cent rule put the meetings in the WBS, as 1.3 and 4.6. The hours came from trimming other estimates, never from adding hours nobody had.

Checking it against Chapter 13

In Chapter 13 the team estimated 64 hours to build and test the seventeen functional requirements. In the WBS those features live in five work packages: frontend 18, backend 16, database 10, authentication and validation 12, and unit tests 8, which add up to 64. The two estimates agree because they were made together.

The rest of Module 2's hours are work no single requirement owns: error handling across the whole application, the reviews and stand-ups, integration, system, load and security testing, deployment and the documents. This is the work students leave out when they estimate from the feature list alone, and the reason their plans run out of time.

The Could Haves of Chapter 13 have no work package on purpose. They are contingency: built only if other work finishes early, which in the event it did not.

An extract of the WBS dictionary

NumberDescriptionDone whenDepends on
2.3 SRSthe software requirements specification: purpose, scope, users, FR-1 to FR-17, NFR-1 to NFR-12, constraints and assumptionsv1.0 walked through with the owner and the counter staff, their changes made, and accepted by the guide2.1, 2.2
3.2 Database schemathe tables, keys, constraints and indexes, as a SQL file that builds an empty databasethe file runs without error on MySQL, and every entity of the ER diagram has its table3.1
6.3 APKan Android app that opens the canteen pages, built as a signed APKinstalled on two phones on the college Wi-Fi, a student places an order through it4.1, 6.2

Do this for your project

  1. Put the project at the top and MU's deliverables beneath it: proposal, requirements, design, application, testing, deployment, documents.
  2. Break each down until every piece can be estimated, owned by one person and recognised as finished.
  3. Check the 100 per cent rule: nothing missing (meetings, reviews, testing, deployment), nothing counted twice, nothing out of scope.
  4. Number every element and never reuse a number.
  5. Estimate every work package in hours; check that the total fits your team's hours.
  6. Write the WBS dictionary, with a real "done when" for each work package.
  7. Draw it in a form that fits your report, and keep the outline for daily use.
munotes.in92

The Work Breakdown Structure

Mistakes that cost marks

A WBS of activities. "Coding", "Testing", "Meeting" at the top level is a to-do list with no structure of deliverables.

Forgotten work. No meetings, no reviews, no testing, no deployment, no documents: the most common gaps, all caught by the 100 per cent rule.

A WBS that does not add up. Estimates totalling far more than the team's hours are a plan that cannot happen.

Work packages with no owner, or with "all" as the owner, which in practice means nobody.

"Done when" left empty, so nothing can ever be shown to be finished.

Quick revision

  • A WBS is a deliverable-oriented, hierarchical breakdown of all the project's work.
  • The bottom level is the work package: estimated, owned by one person, recognisable as done.
  • The 100 per cent rule: the WBS holds all the scope's work and nothing else; parts add up to their parent.
  • Upper levels are deliverables (nouns); elements are numbered (2, 2.3, 2.3.1); each piece of work appears once.
  • The WBS dictionary gives each work package a description, deliverable, done when, owner, estimate and dependencies.
  • The WBS feeds the estimate, the schedule, the resource plan, progress tracking and risk.

Questions you must be able to answer

1. What is a work breakdown structure, and what is a work package? A hierarchical breakdown of the whole scope of a project into deliverables and smaller components. The work package is its lowest level: a piece of work small enough to be estimated with confidence, assigned to one person, scheduled, and recognised as finished.

2. State the 100 per cent rule and explain what it catches. The WBS must include all the work in the project's scope and no work outside it, so that at every level the parts add up exactly to their parent. It catches forgotten work such as testing, reviews and deployment, work counted twice, and work that is not in scope.

3. Why are the upper levels of a WBS named with nouns? Because a WBS breaks down what the project produces, its deliverables, rather than the activities that produce them; the sequence of activities belongs in the schedule.

4. What is a WBS dictionary, and which field matters most? A table describing each work package: its description, deliverable, completion criterion, owner, estimate and dependencies. The completion criterion, "done when", matters most, because without it nothing can be shown to be finished.

munotes.in93

The Work Breakdown Structure

5. How did the worked team check that its WBS was realistic? Its work packages add up to 240 person-hours, exactly four students at 60 hours each, and its feature work packages add up to 64 hours, the same figure as its estimate for the functional requirements in Chapter 13.

Contents This chapter on its own page

munotes.in94

Chapter Seventeen

The Project Timeline: Estimates, Dependencies and the Gantt Chart

Syllabus topic Module 1, "Software Development Life Cycle (SDLC) Planning: ... Project timeline (Gantt Chart)".

In one line

A project timeline turns the work breakdown into a calendar: each piece of work gets an estimated length and a list of the work it must wait for, a calculation finds the longest chain of dependent work, called the critical path, and a Gantt chart draws the result as bars on a calendar.

In the wording to use when asked: project scheduling estimates the duration of each activity, establishes the dependencies between activities, and computes early and late start and finish times by a forward and a backward pass; the activities with zero float form the critical path, which fixes the project's minimum duration; the schedule is presented as a Gantt chart of activities against time, with dependencies and milestones.

From work packages to time

The WBS of Chapter 16 says what has to be done and how much effort each piece takes. It does not say when. Three more things are needed for that:

  1. A duration for each piece of work: how many working days it will be in progress.
  2. Its dependencies: which pieces must finish before it can start.
  3. A calendar: the date work begins, which days are working days, and which are holidays.

From those three, the schedule can be calculated rather than guessed.

Estimating durations

Effort is not duration. Effort is the number of person-hours a task takes; duration is the number of working days it is in progress. The worked team's SRS takes 20 hours of effort (Chapter 16). Aditi, who owns it, puts in 8 of them and the other three put in 4 each (Chapter 18). Alongside her other papers she can give it about an hour on a working day, so it lasts eight working days, and the others fit their hours inside the same eight. Estimating a duration means estimating the effort, then asking honestly how many hours a day the people doing it can give.

Four ways to estimate, often used together:

  • Expert judgement: someone who has done similar work makes the estimate.
  • Analogy: compare with a similar task done before. "The Express project last semester took us two weeks for the backend."
  • Bottom-up: estimate the smallest pieces and add them up, which is what a WBS makes possible.
  • Three-point estimation: ask for three numbers instead of one: an optimistic estimate O, if everything goes well; a most likely estimate M; and a pessimistic estimate P, if things go badly. The PERT formula weights the most likely four times:

expected duration = (O + 4M + P) / 6

For the SRS, the team's three estimates were 6, 8 and 10 working days:

expected duration = (6 + 4 × 8 + 10) / 6 = 48 / 6 = 8 days

munotes.in95

The Project Timeline: Estimates, Dependencies and the Gantt Chart

PERT also gives a measure of how uncertain an estimate is, (P - O) / 6, which for the SRS is 4 / 6, about two thirds of a day. A task whose optimistic and pessimistic estimates are far apart deserves a closer eye, whatever its expected duration.

Dependencies

A dependency says that one piece of work cannot start, or cannot finish, until another has started or finished. There are four kinds, and the first covers nearly every real case:

KindMeaningExample
Finish to startB cannot start until A has finishedthe UML set cannot start until the SRS is finished
Start to startB cannot start until A has startedtesting can start once building has started
Finish to finishB cannot finish until A has finishedthe user manual cannot be finished until the screens are finished
Start to finishB cannot finish until A has startedtaking orders on paper slips cannot end until the pre-order system has started

A lag is a wait added to a dependency: "the review can start two days after the draft is sent". A lead is an overlap: "testing can start three days before building finishes".

Milestones

A milestone is a point in time, not a piece of work: it has no duration. It marks something achieved or an event that happens: the proposal approved, the design review held, the report submitted. Milestones are what a guide checks and what the team celebrates, and they are where your college's assessment dates belong.

The critical path method

With durations and dependencies known, a short calculation gives every task four numbers. Count working days from 0, the first day of the project. A task that starts on day 31 and lasts 3 days works on days 31, 32 and 33; its finish, 34, is the day after its last day, which is also the first day a task waiting for it can start.

The forward pass, from the start of the project to the end, gives each task its early start (ES), the earliest it can begin, which is the latest early finish of all the tasks it waits for; and its early finish (EF), which is its early start plus its duration. The largest early finish in the project is the project length.

The backward pass, from the end back to the start, gives each task its late finish (LF), the latest it can finish without delaying the project, which is the earliest late start of all the tasks that wait for it; and its late start (LS), which is its late finish minus its duration.

munotes.in96

The Project Timeline: Estimates, Dependencies and the Gantt Chart

Float, also called slack, is late start minus early start: how many days a task can slip without delaying the whole project. Strictly this is total float. Free float is the smaller amount a task can slip without delaying even the next task: the earliest early start of the tasks waiting for it, minus its own early finish. When a task leads straight into a critical one, as every task with float does in the worked plan, the two are equal.

The critical path is the chain of tasks with zero float, and its length is the project length. A day lost on a critical task is a day lost on the whole project; a day lost on a task with four days of float is not, until the float is used up. That one fact tells you where to watch most closely, where extra help makes the project shorter, and where it makes no difference at all.

Some textbooks count days from 1 and write a task's finish as its last day. The critical path and every float come out the same either way; only the day numbers shift by one.

The worked project: the schedule, computed

The worked team took sixteen tasks from its WBS and its SDLC plan (Chapter 15), each with a duration in working days and the tasks it waits for, and wrote a short program to do the two passes, so that a change to any estimate would recompute everything. It is written in JavaScript, the language of the whole application, it runs with Node.js, which Chapter 39 installs, and it lives in the repository beside the Gantt source, in docs/plan.

// cpm.js: the critical path of the team's plan, and the dates
// of every task. Run it from the project folder:
//   node docs/plan/cpm.js
//
// Each task is an id, a name, its length in working days, and
// the ids of the tasks it waits for. A task is always listed
// after the tasks it waits for.
const tasks = [
  ['C', 'Choose problem', 5, []],
  ['O', 'Observe, survey', 5, ['C']],
  ['I', 'Interviews', 4, ['O']],
  ['P', 'Proposal', 5, ['I']],
  ['S', 'SRS', 8, ['I']],
  ['U', 'UML set', 5, ['S']],
  ['W', 'Wireframes', 3, ['S']],
  ['D', 'Schema, API', 4, ['U']],
  ['A', 'Architecture', 3, ['D', 'W', 'P']],
  ['F', 'Frontend', 12, ['A']],
  ['B', 'Backend', 10, ['A']],
  ['V', 'Auth, validation', 6, ['B']],
  ['T', 'Testing', 5, ['F', 'V']],
  ['L', 'Load, security', 3, ['T']],
  ['Y', 'Deploy, APK', 4, ['V']],
  ['R', 'Report', 7, ['L', 'Y']],
];

// Forward pass: a task starts as soon as the last of the
// tasks it waits for has finished.
const early = {};
for (const [id, , days, after] of tasks) {
  const finishes = after.map((a) => early[a].finish);
  const start = Math.max(0, ...finishes);
  early[id] = { start, finish: start + days };
}
const end = Math.max(...tasks.map(([id]) => early[id].finish));

// Backward pass: a task must finish by the time the first
// task waiting for it has to start, or by the end.
const late = {};
for (const [id, , days] of [...tasks].reverse()) {
  const waiting = tasks.filter((t) => t[3].includes(id));
  const starts = waiting.map((t) => late[t[0]].start);
  const finish = Math.min(end, ...starts);
  late[id] = { start: finish - days, finish };
}

// Working day n as a date. Day 0 is Monday 27 July 2026, and
// weekends and holidays are not counted. JavaScript counts
// months from 0, so month 6 is July.
const HOLIDAYS = ['2026-10-02'];
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

function isWorkingDay(d) {
  const weekend = d.getUTCDay() === 0 || d.getUTCDay() === 6;
  const ymd = d.toISOString().slice(0, 10);
  return !weekend && !HOLIDAYS.includes(ymd);
}

function dateOf(n) {
  const d = new Date(Date.UTC(2026, 6, 27));
  let counted = 0;
  while (counted < n) {
    d.setUTCDate(d.getUTCDate() + 1);
    if (isWorkingDay(d)) counted += 1;
  }
  const day = String(d.getUTCDate()).padStart(2, '0');
  return `${day} ${MONTHS[d.getUTCMonth()]}`;
}

// The table. A task with early start 31 and early finish 34
// works on days 31, 32 and 33, so its last day is finish - 1.
const pad = (v, n) => String(v).padStart(n);
console.log('Id Task             Days  ES  EF  LS  LF'
  + ' Float  Dates');
for (const [id, name, days] of tasks) {
  const e = early[id];
  const l = late[id];
  console.log(`${id}  ${name.padEnd(16)}${pad(days, 5)}`
    + `${pad(e.start, 4)}${pad(e.finish, 4)}`
    + `${pad(l.start, 4)}${pad(l.finish, 4)}`
    + `${pad(l.start - e.start, 6)}`
    + `  ${dateOf(e.start)} to ${dateOf(e.finish - 1)}`);
}

const critical = tasks.filter(([id]) =>
  late[id].start === early[id].start).map(([id]) => id);
console.log(`Project length: ${end} working days,`
  + ` last day ${dateOf(end - 1)}`);
console.log(`Critical path: ${critical.join(' ')}`);
munotes.in97

The Project Timeline: Estimates, Dependencies and the Gantt Chart

Run from the project folder, it prints:

$ cd canteen-preorder
$ node docs/plan/cpm.js
Id Task             Days  ES  EF  LS  LF Float  Dates
C  Choose problem      5   0   5   0   5     0  27 Jul to 31 Jul
O  Observe, survey     5   5  10   5  10     0  03 Aug to 07 Aug
I  Interviews          4  10  14  10  14     0  10 Aug to 13 Aug
P  Proposal            5  14  19  26  31    12  14 Aug to 20 Aug
S  SRS                 8  14  22  14  22     0  14 Aug to 25 Aug
U  UML set             5  22  27  22  27     0  26 Aug to 01 Sep
W  Wireframes          3  22  25  28  31     6  26 Aug to 28 Aug
D  Schema, API         4  27  31  27  31     0  02 Sep to 07 Sep
A  Architecture        3  31  34  31  34     0  08 Sep to 10 Sep
F  Frontend           12  34  46  38  50     4  11 Sep to 28 Sep
B  Backend            10  34  44  34  44     0  11 Sep to 24 Sep
V  Auth, validation    6  44  50  44  50     0  25 Sep to 05 Oct
T  Testing             5  50  55  50  55     0  06 Oct to 12 Oct
L  Load, security      3  55  58  55  58     0  13 Oct to 15 Oct
Y  Deploy, APK         4  50  54  54  58     4  06 Oct to 09 Oct
R  Report              7  58  65  58  65     0  16 Oct to 26 Oct
Project length: 65 working days, last day 26 Oct
Critical path: C O I S U D A B V T L R
munotes.in98

The Project Timeline: Estimates, Dependencies and the Gantt Chart

Read a few rows by hand to see the method working.

Architecture (A) waits for D, W and P, which finish at 31, 25 and 19. Its early start is the largest of those, 31, and its early finish is 31 + 3 = 34: it works on 8, 9 and 10 September.

Frontend (F) could start at 34 and finish at 46. But the only task waiting for it is Testing (T), which cannot start before 50 anyway, because T also waits for V, which finishes at 50. So F may finish as late as 50, may start as late as 38, and has 4 days of float.

The Proposal (P) has 12 days of float: nothing waits for it except the architecture document, which cannot start before day 31. The team did not spend that float. The proposal is the document the guide reads first, and a proposal read early is feedback received early, so they finished it on 20 August as planned. Float is permission to slip, not an instruction to.

The critical path is C O I S U D A B V T L R, 65 working days: from choosing the problem, through the SRS, the UML, the schema and the architecture, the backend and its sign-in and validation, and the testing, to the report. A day lost on the backend is a day lost on the whole project; a day lost on the frontend is not, until four days are gone.

Against the time available

The semester gives the team 15 weeks from 27 July: 75 weekdays, less the holiday on 2 October, which is 74 working days. The critical path needs 65. That leaves 9 working days of slack for college events, examinations in other papers, and the things that always go wrong. This slack belongs to the whole project, measured against the deadline; it is not the float of any one task. Nine days is not a large margin, so the team watched the critical tasks at every stand-up.

munotes.in99

The Project Timeline: Estimates, Dependencies and the Gantt Chart

The architecture document, the last of the design documents, is finished on Thursday 10 September, in the seventh week, and the design review is held on the last day of that week, Friday 11 September. The last day of work is Monday 26 October.

When the plan is too long

Had the critical path come to 80 days against 74, there are only three honest ways to shorten it:

  • Crashing: put more people on a critical task. Help on a task with float shortens nothing.
  • Fast-tracking: overlap tasks that were planned in sequence, such as starting the UML set before the SRS is completely finished. It saves time and adds the risk of rework.
  • Cutting scope: build less. This is what the Could Haves of Chapter 13 are for.

Hoping to work faster is not on the list.

The Gantt chart

A Gantt chart draws the schedule as horizontal bars against a calendar: one row per task, each bar starting at the task's start date and as long as its duration. Arrows show dependencies, diamonds show milestones, and many tools can also mark today's date and shade each bar to show how much of it is done.

It is the most readable form of a schedule, and the form the syllabus names for the project timeline. It is a picture of the schedule, though, not the calculation: the numbers come from the method above, and the chart shows them.

The worked team drew its Gantt chart with PlantUML, from the same tasks and dependencies:

A Gantt chart of sixteen tasks from 27 July to 26 October 2026, with columns of start dates, end dates and durations on the left, bars against the weeks of the year, arrows between dependent tasks, and diamonds for the design review and the submission

Figure 17.1 The worked team's Gantt chart, drawn by PlantUML from the source below

@startgantt gantt
printscale weekly
saturday are closed
sunday are closed
Project starts 2026-07-27
2026-10-02 is closed

[Choose problem] requires 5 days
[Observe, survey] requires 5 days
[Observe, survey] starts at [Choose problem]'s end
[Interviews] requires 4 days
[Interviews] starts at [Observe, survey]'s end
[Proposal] requires 5 days
[Proposal] starts at [Interviews]'s end
[SRS] requires 8 days
[SRS] starts at [Interviews]'s end
[UML set] requires 5 days
[UML set] starts at [SRS]'s end
[Wireframes] requires 3 days
[Wireframes] starts at [SRS]'s end
[Schema, API] requires 4 days
[Schema, API] starts at [UML set]'s end
[Architecture] requires 3 days
[Architecture] starts at [Schema, API]'s end
[Architecture] starts at [Wireframes]'s end
[Architecture] starts at [Proposal]'s end
[Design review] happens 2026-09-11
[Frontend] requires 12 days
[Frontend] starts at [Architecture]'s end
[Backend] requires 10 days
[Backend] starts at [Architecture]'s end
[Auth, validation] requires 6 days
[Auth, validation] starts at [Backend]'s end
[Testing] requires 5 days
[Testing] starts at [Frontend]'s end
[Testing] starts at [Auth, validation]'s end
[Load, security] requires 3 days
[Load, security] starts at [Testing]'s end
[Deploy, APK] requires 4 days
[Deploy, APK] starts at [Auth, validation]'s end
[Report] requires 7 days
[Report] starts at [Load, security]'s end
[Report] starts at [Deploy, APK]'s end
[Submission] happens at [Report]'s end
@endgantt
munotes.in100

The Project Timeline: Estimates, Dependencies and the Gantt Chart

A few details of the source are worth copying:

  • Project starts 2026-07-27 fixes day 0. saturday are closed and sunday are closed make weekends non-working days, and 2026-10-02 is closed does the same for the holiday, so every date PlantUML computes is a working date.
  • Each requires line is a duration in working days, and each starts at [X]'s end line is a finish-to-start dependency.
  • [Submission] happens at [Report]'s end is a milestone that follows a task: it moves if the report moves.
  • [Design review] happens 2026-09-11 is a milestone on a fixed date, because the date is the guide's to set, not the plan's. The chart shows at a glance that the architecture document, the last design task, ends the day before.
  • printscale weekly gives one column to a week, so the whole semester fits a page. The numbers along the top are the weeks of the year.

Two things in the drawing need reading with care. The Duration column counts calendar days, weekends included: the proposal's five working days, Friday 14 to Thursday 20 August, show as 7 days. And each milestone is listed as lasting one day, which is only how PlantUML draws a diamond; a milestone takes no time at all.

PlantUML computed its dates independently of the program above, and its Start and End columns agree with it on every task. Two separate calculations from the same tasks agreeing is a good check that neither contains a slip. It cannot check the estimates themselves, which are only as good as the team's judgement.

Putting your college's dates in

Put each of your guide's assessment dates into the plan the same way as the design review: one line each, on a fixed date. If your code review were fixed for Thursday 1 October, the line would be:

[Code review] happens 2026-10-01

A milestone on a fixed date shows whether the work it needs will be ready in time. If a bar that must finish before the review ends after the diamond, the plan has a problem, and it is far better to see it in August than on the day.

Keeping the timeline honest

A schedule made in August and never looked at again is a decoration. Every week, the worked team did three things at the Tuesday stand-up:

  1. Marked what had finished, and how much of each task in progress was done.
  2. Re-estimated anything that was clearly going to take longer.
  3. Re-ran the program. If the project length grew past 74 working days, they decided what to cut, starting with the Could Haves (Chapter 13).
munotes.in101

The Project Timeline: Estimates, Dependencies and the Gantt Chart

In the event, the stock rule in the backend took longer than its estimate (Chapter 45). The backend is on the critical path, so the extra days could not come out of any task's float: they came out of the nine days of slack before the end of the semester. That slack was also the only time the Could Haves would ever have had, so none of them was built, which is exactly the outcome Chapter 13 planned for.

Do this for your project

  1. Take the work packages from your WBS. Estimate each one's effort, then its duration from the hours a day its owner can really give.
  2. For uncertain tasks, use three-point estimates and the PERT formula.
  3. For each task, list the tasks it must wait for; most will be finish to start.
  4. Do the forward and backward passes, by hand for a small plan or with a short program like the one above.
  5. Find the critical path and the float of every task. Compare the project length with the working days you have, leaving slack.
  6. Draw the Gantt chart, and add your college's assessment dates as fixed-date milestones.
  7. Update it every week, and re-run the calculation whenever an estimate changes.

Mistakes that cost marks

A Gantt chart with no dependencies. Bars placed by eye cannot tell you what a delay will do.

Effort used as duration. Twenty hours of work is not two and a half days for a student with other papers to study.

No holidays, no examinations in other papers. A plan that assumes every weekday is free is already late.

No critical path. Without it, nobody knows which delays matter.

Extra help put on a task with float. It makes that task shorter and the project no shorter at all.

A plan never updated. The guide will ask where you are against it, and "the chart is from August" is not an answer.

Quick revision

  • Effort is person-hours; duration is working days in progress; convert using the hours a day people can really give.
  • Estimate by judgement, analogy, bottom-up, or three-point: expected = (O + 4M + P) / 6, uncertainty = (P - O) / 6.
  • Dependencies: finish to start (almost all), start to start, finish to finish, start to finish; with lags and leads.
  • Milestones have no duration; your college's assessment dates are milestones on fixed dates.
  • Forward pass gives ES and EF; backward pass gives LS and LF; total float = LS minus ES.
  • The critical path is the zero-float chain; its length is the project length.
  • A plan that is too long is shortened by crashing, fast-tracking or cutting scope.
  • A Gantt chart draws the schedule as bars against a calendar, with dependency arrows and milestone diamonds.
munotes.in102

The Project Timeline: Estimates, Dependencies and the Gantt Chart

Questions you must be able to answer

1. What is the difference between effort and duration? Effort is the number of person-hours a task needs; duration is the number of working days it is in progress. Duration depends on how many hours a day the people doing it can give, so eight hours of effort from a student who can give it an hour a day lasts eight working days.

2. Using three-point estimation, what is the expected duration of a task estimated at 6, 8 and 13 days? (6 + 4 × 8 + 13) / 6 = 51 / 6, which is 8.5 days. It is longer than the most likely estimate because the pessimistic estimate is further from it than the optimistic one.

3. Define float and the critical path. Float is how long a task can be delayed without delaying the project: its late start minus its early start. The critical path is the chain of tasks with zero float; its length is the minimum duration of the whole project, and any delay on it delays the project.

4. Why does the worked team's frontend have 4 days of float? Because the only task waiting for it, testing, also waits for the sign-in and validation work, which finishes on day 50, four days after the frontend's early finish on day 46.

5. What is a milestone, and how would you show your college's code review date on a Gantt chart? A milestone is a point in time with no duration, marking an achievement or an event. The review is added as a milestone on a fixed date, and the chart then shows whether the work it needs will be finished before it.

6. How long is the worked team's critical path, and how much slack does the semester leave? 65 working days. The 15 weeks from 27 July hold 74 working days once the 2 October holiday is removed, leaving 9 working days of slack.

7. The project is five days too long. Would two extra people on the frontend help? No. The frontend has four days of float, so shortening it does not shorten the project. Help must go to a task on the critical path, or tasks must be overlapped, or scope cut.

Contents This chapter on its own page

munotes.in103

Chapter Eighteen

Resource Planning

Syllabus topic Module 1, "Software Development Life Cycle (SDLC) Planning: ... Resource planning".

In one line

Resource planning works out everything the project needs besides the plan itself: people's hours and skills, machines, software and services, money, and other people's time; when each is needed; and who provides it. It shares out the work so that nobody is overloaded, and it writes down who is responsible for what.

In the wording to use when asked: resource planning identifies the resources each activity requires, human, equipment, software, facilities and funds, estimates the quantity of each and when it is needed, assigns resources to activities against their availability, and resolves over-allocation by resource levelling or smoothing, so that the schedule can actually be carried out; responsibilities are recorded in a responsibility assignment matrix such as RACI.

What counts as a resource

KindIn a mini projectThe question to ask
Peoplethe team's hours and skillswho does it, for how many hours, in which weeks?
Equipmentlaptops, a machine for the server, phones and tablets to test ondoes it exist, may we use it, and from when?
Software and serviceslanguages, tools, a code host, hostingis it free, and does it need an account or a licence?
Moneyanything that must be boughtwho pays, and has anyone agreed to?
Other people's timethe guide, the client, the users, whoever runs the machineswhen do we need them, and have we asked?
Informationdata the system needs before it can start, such as a menu and its priceswho provides it, and by when?

The last two are the ones students forget. A canteen owner's hour is scarcer than a student's, and a design review cannot happen on a day the guide is away. Plan them like any other resource: named, dated, and asked for early.

People: capacity, skills and allocation

Capacity is how many hours each person has. For this paper MU sets it: 2 credits, which is 60 hours for each student, 30 in each module. Over a 15-week semester that is 4 hours a week on average. Project work comes in waves, though, so a team should also agree how much a single week may hold. The worked team's rule was that nobody works more than 10 hours on this paper in any week, because every member has other papers with deadlines of their own.

Skills decide who can do what. The skills matrix of Chapter 6 rated each member on each technology, and the resource plan uses it twice: to give work to people who can do it, and to make sure that no skill the project cannot do without lives in one head only. The number of people who would have to be unavailable before a piece of work stops is often called its bus factor. For anything the demonstration depends on, it should be at least two.

munotes.in104

Resource Planning

Allocation shares each work package's hours among the people who will do them. It passes two checks or it is wrong: every package's hours add up to its estimate in the WBS, and every person's hours add up to their capacity. If either fails, the plan is promising work that nobody has time for.

Checking the load, week by week

An allocation can give everyone exactly 30 hours and still be impossible, if 20 of someone's hours fall in one week. The load check finds this:

  1. Give every work package the dates of its bar in the Gantt chart.
  2. Spread each person's hours on it evenly over those working days, and put each meeting on its own day.
  3. Add up each person's hours, week by week.
  4. Mark every week in which anyone is over the limit. That is over-allocation.

There are three ways to remove an overload:

  • Move the work within its float. A task with float can start later without delaying the project (Chapter 17). Moving it into a quieter week is resource smoothing, and the end date does not change.
  • Give the work to someone else who has spare hours that week, and give that person hours back in a week when the overloaded member is free, so both still total their capacity.
  • Delay the work until the person is free, even beyond its float. This is resource levelling, and when the work is on the critical path the end date moves with it.

Strictly, smoothing keeps the end date and levelling may not; in everyday use many people say "levelling" for both.

Responsibility: the RACI matrix

A responsibility assignment matrix lists the work down the side, the people across the top, and in each cell what that person's part is. The best-known form uses four letters, RACI:

  • R, Responsible: does the work. A row may have several.
  • A, Accountable: answerable for the work being done and done well, and the one who says it is finished. Exactly one per row, and in a student project this is the owner of Chapter 16.
  • C, Consulted: asked for input before or during the work. The conversation goes both ways.
  • I, Informed: told of progress or of the result. One way.

The single A is the point of the matrix. A row with two A's has nobody accountable, because each can say it was the other's; a row with none has the same problem more honestly. The A is not always the person who does most of the work, as the worked project shows.

munotes.in105

Resource Planning

The worked project: the resource plan

Hours, Module 1

Every figure is person-hours. The first column is the WBS number from Chapter 16, whose owners are the A's in the matrix further down.

NumberWork packageAditiFarhanSnehaRohanHours
1.1Problem choice, proposal41218
1.2Plan42-28
1.3Reviews, stand-ups333312
2.1Observation, survey414110
2.2Interviews5-3-8
2.3SRS844420
3.1UML set-74718
3.2Database schema-6-410
3.3API specification-2248
3.4Wireframes--6-6
3.5Architecture document242412
Total30303030120

Two rows are worth a second look. Everyone has hours on the SRS although Aditi owns it: each member wrote the requirements for the part they would build, and Aditi wrote the rest and edited the whole. And Farhan owns the API specification, because the backend must honour it, but Rohan wrote half of it, because he would test against it.

Hours, Module 2

NumberWork packageAditiFarhanSnehaRohanHours
4.1Frontend pages6-12-18
4.2Backend routes, services48-416
4.3Database integration-8-210
4.4Authentication, validation233412
4.5Error handling-21-3
4.6Reviews, stand-ups333312
5.1Unit tests2-158
5.2Integration tests3-2-5
5.3System, acceptance tests2-3-5
5.4Load, security tests-2-24
6.1Local hosting---22
6.2Linux server-2-24
6.3APK---44
6.4GitHub repository2---2
7.1Technical report51118
7.2User manual--3-3
7.3Presentation11114
Total30303030120

Here too the owner is not always the main hand. Rohan is accountable for the integration tests, but Aditi and Sneha run them, in a week when he is deploying. And Rohan's two hours on the server are a practice deployment in the week of 14 September; Farhan's two are the real deployment in October, done from Rohan's notes.

The load check, and what it found

The team's first version of Module 1 put all of the plan, 1.2, in the proposal's week, because the plan goes into the proposal. The check showed the result at once: 12.7 hours for Aditi in the week of 17 August, when her share of the proposal, the whole plan and her share of the SRS all fell together.

munotes.in106

Resource Planning

The fix used two of the three tools. The skeleton of the WBS and the schedule needs only MU's list of deliverables, known from the first day, so Aditi drafted it in the week of 3 August, while the team was observing the canteen: smoothing, since the plan has no bar of its own and the end date did not move. The estimates need the requirements, so they stayed in the proposal's week and went to Farhan and Rohan, who would build and test what they were estimating (Chapter 13). To keep everyone at 30 hours, Aditi took over Farhan's hour at the interviews and one of Rohan's hours of observation.

After the change, week by week:

WeekStartingAditiFarhanSnehaRohanTeam
127 Jul1.251.251.251.255.00
23 Aug7.251.254.251.2514.00
310 Aug7.051.153.951.1513.30
417 Aug8.704.603.804.6021.70
524 Aug2.255.459.655.4522.80
631 Aug0.259.053.359.0521.70
77 Sep3.257.253.757.2521.50
Total30.0030.0030.0030.00120.00

Week 7 includes the design review on Friday 11 September; the build that starts the same day is counted with Module 2. Nobody passes 10 hours in any week. The heaviest is Sneha's week of 24 August, 9.65 hours, when the end of the SRS, her part of the UML set and all six hours of wireframes fall together.

A load check shows spare time as plainly as overload. Aditi's week of 31 August holds only the stand-up. If the UML set, which is on the critical path, ran late, she is the one with time to help: Chapter 17's crashing, planned in advance.

The same check on Module 2's first version found two overloads. In the week of 14 September Farhan had 11.75 hours, and more than 10 again the week after, because the backend and the database were both his, and both are on the critical path, so nothing could wait. In the week of 5 October Rohan had nearly 15 hours: deployment, the APK and the integration tests all fell on him at once.

Deployment has four days of float, and the first idea was to use them. The check showed why that fails: four days later is the week of Rohan's load and security testing, and his overload simply moved there, to more than 14 hours in the week of 12 October. Float helps only when the week it moves work into is quiet.

munotes.in107

Resource Planning

So the team reassigned. Aditi and Rohan took part of the backend and the database in the weeks they were built, and Farhan took hours later, in authentication and validation, load testing and the server, where knowing the backend helps anyway. Aditi and Sneha ran the integration tests. And two of the server hours moved to the week of 14 September, for the practice deployment that Chapter 6 set as the answer to its first risk: nobody on the team had deployed Node.js on a Linux server. After the changes no week in Module 2 is over 8.25 hours, the heaviest being Farhan's week of 14 September.

Responsibility, Module 1

Every row has one A, and it is the owner Chapter 16 named. A dash means not involved.

Work packageAditiFarhanSnehaRohanGuideCanteen owner
1.1 Problem choice, proposalA, RRRRCC
1.2 PlanA, RRCRI-
2.1 Observation, surveyRRA, RRIC
2.2 InterviewsA, RIRIIC
2.3 SRSA, RRRRCC
3.1 UML setCA, RRRC-
3.2 Database schemaIA, RIRI-
3.3 API specificationIA, RRRI-
3.4 WireframesCCA, RIIC
3.5 Architecture documentRA, RRRC-

The guide is consulted on the documents MU assesses and informed of the rest. The canteen owner is consulted wherever the system's behaviour or her canteen's facts are decided, and on the wireframes, because she and the counter staff are the ones who will use the screens during the rush. The meetings, 1.3, are left out: everyone attends them.

Equipment, software, services and money

ResourceWhat the worked team usedCost to the teamFirst needed
Development machinesthe members' own laptops: three on Windows 11, one a MacBook (Chapter 6)nothingweek 1
A machine for the serveran old desktop in the college lab, reinstalled with Ubuntu 24.04 by the IT lab in-chargenothingweek 8, for the practice deployment
A device at the counterthe owner's Android phone for the trial; a tablet at Rs 9,500, which Chapter 7 costs against the canteen's own savingsnothingthe trial
Phones to test the APKtwo of the members' own Android phonesnothingweek 11
SoftwareNode.js, MySQL Community Server, Visual Studio Code, Git, PlantUML and Android Studio, all freenothingbefore each is first used
Servicesa free GitHub account and one repositorynothingthe first commit (Chapter 39)
Networkthe college Wi-Fi, which reaches the whole canteen (Chapter 6)nothingthe trial
Moneynone: constraint C-2 (Chapter 14)nothing-
munotes.in108

Resource Planning

A plan in which every cost is "nothing" is still a plan: each line says where the resource comes from and when, and the two lines that depend on other people, the lab machine and the owner's phone, have been asked for.

People outside the team

WhoWhat the team needs from themWhen
Prof. S. Iyer, the guidethe proposal review, the design review and the code review; a look at each increment21 Aug, 11 Sep, 1 Oct; 24 Sep and 9 Oct
Lata Pawar, the canteen owneran interview; the menu and its prices; the two increment reviews10 Aug; before the build; 24 Sep and 9 Oct
Ganesh More, at the counteran interview and two lunch breaks of observation; the increment reviews11 Aug; 24 Sep and 9 Oct
The head cookan interview in the kitchen; the second increment review12 Aug; 9 Oct
Six students of different yearsa group interview13 Aug
The IT lab in-chargean interview; the lab desktop reinstalled, and kept on during college hours13 Aug; before week 8

The interview dates are the ones Chapter 9 recorded, and the increment reviews are Chapter 15's. The guide's dates are the guide's to set; every other date was asked for as soon as the team knew it would need it.

Do this for your project

  1. List your resources under the six kinds. For each, write where it comes from and the week it is first needed.
  2. Write each person's capacity: MU's 30 hours a module, and your team's own weekly limit.
  3. Share every work package's hours among people. Check both sums: each package equals its WBS estimate, and each person equals their capacity.
  4. Do the load check: spread the hours over the Gantt bars, add them up by week, and mark every overload.
  5. Remove each overload by moving work within its float, reassigning it, or delaying it, and check again. A move can shift an overload rather than remove it.
  6. Give every skill the demonstration depends on a second person.
  7. Write the RACI matrix with exactly one A in each row.
  8. Book other people's time now: the guide's review dates, the client's reviews, the machine.
  9. Put the resource plan in your proposal (Chapter 33), and repeat the load check whenever the schedule changes.

Mistakes that cost marks

Hours that add up but do not fit. Thirty hours each is not enough; the weeks have to fit as well.

"All" in every row. An allocation in which everybody does everything hides who is short of time and who is accountable.

One person for a critical skill. If only one member can deploy, a fever in the deployment week ends the demonstration.

munotes.in109

Resource Planning

Other people's time assumed. The guide's calendar and the client's lunch break are resources. Ask for them early.

Meetings left out. Fifteen minutes a week comes to between an hour and a half and two hours a module for each member, and that time has to come from somewhere.

Float used without looking. Moving a task can move an overload instead of removing it.

Quick revision

  • Resources: people (hours and skills), equipment, software and services, money, other people's time, information.
  • Capacity for this paper: 60 hours each, 30 per module, about 4 a week on average; agree a weekly limit as well.
  • Allocation passes two checks: each package equals its estimate, and each person equals their capacity.
  • Load check: spread the hours over the Gantt bars and add them by week; over-allocation is any week over the limit.
  • Fixes: smoothing (within float, end date kept), reassignment, levelling (may move the end date).
  • RACI: Responsible does it; Accountable, exactly one per row, answers for it; Consulted gives input; Informed is told.
  • Bus factor: every critical skill should live in at least two heads.

Questions you must be able to answer

1. What does resource planning cover in a mini project? The people's hours and skills, the equipment, software and services, any money, other people's time and the information the system needs; when each is needed and who provides it; an allocation of the work that overloads nobody; and a record of who is responsible for what, usually a RACI matrix.

2. What is the difference between resource smoothing and resource levelling? Smoothing moves work only within its float, so resource peaks are reduced and the end date does not change. Levelling moves work until the resource is available, even beyond its float, so if the work is on the critical path the end date moves too.

3. What do R, A, C and I stand for, and why must each row have exactly one A? Responsible, who does the work; Accountable, who answers for it and says when it is finished; Consulted, who is asked for input; and Informed, who is told of the result. With two A's each can blame the other, and with none nobody answers for the work, so exactly one is needed.

4. The worked team's first allocation gave every member 30 hours in Module 1. What was still wrong with it? The hours were in the wrong weeks. With the whole plan placed in the proposal's week, Aditi had 12.7 hours in the week of 17 August, over the team's limit of 10. Drafting the plan's skeleton in the week of 3 August and giving the estimates to Farhan and Rohan brought her heaviest week down to 8.7 hours.

munotes.in110

Resource Planning

5. Why did using the deployment's float not remove Rohan's overload? Because the four days of float moved the deployment into the week of his load and security testing, where his total came to more than 14 hours instead. Float removes an overload only when the week the work moves into has room for it; otherwise the work must go to someone else.

6. What is a bus factor, and how did the worked team deal with its weakest skill? The number of people who would have to be unavailable before a piece of work stops. Nobody on the team had deployed Node.js on a Linux server, so Rohan made a practice deployment in the week of 14 September and Farhan made the real one in October from Rohan's notes, leaving two members who had done it.

Contents This chapter on its own page

munotes.in111

Chapter Nineteen

System Modeling with UML: the Six Diagrams and How They Fit Together

Syllabus topic Module 1, "System Modeling using UML: Use Case Diagram, Class Diagram, Sequence Diagram, Activity Diagram, ER Diagram, Deployment Diagram".

In one line

A model is a simplified description of a system, made to answer questions about it before it is built; UML is the standard notation for software models, and MU's six diagrams each answer one question: who uses the system and for what, what it knows about, what happens step by step in one scenario, how a process flows, what data it stores, and where it runs. Together they must describe one system, and agree.

In the wording to use when asked: the Unified Modeling Language (UML) is a general-purpose, standard visual modelling language maintained by the Object Management Group, whose stated objective is to give architects, engineers and developers tools for the analysis, design and implementation of software-based systems; UML 2.5.1 defines fourteen diagram types in two families, structure diagrams, which show the static structure of a system, and behaviour diagrams, which show its dynamic behaviour. The entity-relationship (ER) diagram is not part of UML but is used alongside it for database design.

Why draw models at all

A diagram is cheaper to change than code. Moving an arrow takes a minute; moving a feature that three other features depend on takes a week. A model lets a team find its mistakes while they are still arrows.

A model also answers questions that code answers badly. "Which users can cancel an order?" is one glance at a use case diagram and an afternoon's reading of source files. "What happens if two students order the last biryani at the same moment?" is a sequence diagram's whole purpose.

And a model is how a team, a guide and an examiner talk about a system without reading its code. The guide awards 5 marks for "System Design (SRS, UML Diagrams, Architecture)", and the examiner 10 for "Technical Design & Implementation" (Chapter 1). The UML set is where the design is visible.

The UML specification puts the first principle in one sentence: "A model is always a model of something." Every diagram leaves most of the system out on purpose, so that one question can be answered clearly. That is why there are several kinds.

What UML is

Unified. In its own account, UML 1 grew out of three object-oriented methods of the time, called Booch, OMT and OOSE, which it combined into one notation.

A standard. It is maintained by the Object Management Group (OMG), a standards body. The OMG adopted its first formal version, UML 1.1, in December 1997. The latest formal version is UML 2.5.1, published in December 2017, and on 30 September 2026 the OMG's own page still listed it as the latest. When a guide asks which UML you used, that is the answer.

A language, not a method. UML says how to draw a use case or a class; it does not say which diagram to draw first, how many to draw or when a design is finished. That is the job of your SDLC (Chapter 15) and of MU's syllabus, which names six diagrams.

munotes.in112

System Modeling with UML: the Six Diagrams and How They Fit Together

One model, many views. The diagrams are not separate pictures that happen to share names. They are views of one model of one system: the Order in the class diagram is the same Order that appears in the sequence diagram and becomes a table in the database.

Fourteen kinds of diagram in two families

Annex A of the UML 2.5.1 specification divides its diagrams into two families:

  • Structure diagrams show the static structure of the system: its parts and how they relate, irrespective of time.
  • Behaviour diagrams show its dynamic behaviour: what happens, in what order, as time passes.
FamilyDiagram kindsMU names
Structureclass, object, package, component, composite structure, deployment, profileclass, deployment
Behaviouruse case, activity, state machineuse case, activity
Behaviour, the interaction diagramssequence, communication, interaction overview, timingsequence

That is seven structure kinds and seven behaviour kinds, fourteen in all. The specification adds that the boundaries between the kinds are not strictly enforced: a diagram may mix structural and behavioural elements where that helps.

MU asks for five of the fourteen, plus one diagram that is not UML at all.

Where the ER diagram sits

The entity-relationship (ER) diagram comes from database design, not from UML. Peter Chen published the entity-relationship model in 1976, in the first issue of the journal ACM Transactions on Database Systems, twenty-one years before UML 1.1.

MU is right to list it with the UML diagrams, because every project with a database needs it. UML's class diagram can describe data too, but an ER diagram speaks the database's own language: entities, their keys, and how many of one relate to how many of another. The class diagram describes the objects the program works with; the ER diagram describes the tables they are stored in. Chapter 24 draws the worked ER diagram in both of its common notations.

MU's six, and the question each answers

DiagramFamilyThe question it answersDrawn fromChapter
Use casebehaviourwho uses the system, and for what?the functional requirements and the stakeholders20
Classstructurewhat kinds of thing does the system know about, what does each hold, and how are they related?the nouns of the requirements21
Sequencebehaviour: interactionin one scenario, who sends which message to whom, and in what order?one use case at a time22
Activitybehaviourhow does a process flow, with its decisions and parallel paths, and who does each step?a use case, or a process of the organisation23
ERnot UML: data modellingwhat data is stored, and how are the records related?the classes whose data must be kept24
Deploymentstructurewhere does each piece of software run, and how do the machines talk to each other?the architecture and the hosting plan25
munotes.in113

System Modeling with UML: the Six Diagrams and How They Fit Together

Read the "drawn from" column as an order of work. The use case diagram comes straight out of the requirements. The class diagram comes from the things the use cases talk about. Sequence and activity diagrams show how the use cases actually happen, using those classes. The ER diagram stores what the classes hold. The deployment diagram puts it all on machines.

How the six must agree

Six diagrams that each look right can still describe six different systems. A complete UML set, the Module 1 deliverable (Chapter 35), is six diagrams of one system, and these checks prove it:

  1. Every functional requirement is covered by a use case, and every use case traces back to at least one requirement.
  2. Every actor is a user class or an outside system named in the SRS. An actor that appears nowhere else is a stakeholder somebody forgot to interview.
  3. Every important use case has a sequence or an activity diagram showing how it happens.
  4. Every message a sequence diagram sends to a part of the system is something that part can do: an operation of its class, or a route of the API.
  5. Every class whose data must survive a restart appears in the ER diagram, with the same attributes.
  6. Every artifact in the deployment diagram is something the architecture says will be built, and every node is a machine that really exists.
  7. One thing, one name. An Order is not an OrderRecord in one diagram and a Purchase in another. Between code and database one mapping rule is allowed and should be stated, such as camelCase in JavaScript and snake_case in SQL, so that pickupDate is pickup_date. A name that breaks the rule is written down, with the one place in the code that translates it.

The worked project, checked

The worked team ran these checks before its design review, and again once the code existed (Chapter 35). Two examples show what agreeing looks like.

Placing an order. Use case UC-4, Place an order, covers FR-7 to FR-11. Two sequence diagrams show it happening: the page and the server, then the transaction inside the server that takes the stock. Every class they touch, Order, OrderItem and MenuItem, is in the class diagram, and each is a table in the ER diagram: orders, order_items and menu_items. The code that does it is the canteen-preorder artifact that the deployment diagram places on the server.

munotes.in114

System Modeling with UML: the Six Diagrams and How They Fit Together

An order's status. The class diagram gives Order a status with six values: placed, preparing, ready, collected, cancelled and no_show. The state machine diagram has exactly those six states, and five arrows between them. The database stores the status in a column that allows exactly those six values, and the application's rules allow exactly those five moves (Chapter 43). Four places, one set of names.

And the checks found mistakes. The class diagram first called the line of an order OrderLine, while the table was order_items and the API sent a list called items, so the class was renamed OrderItem: one thing, one name. It also lacked three attributes the tables have, a menu item's category and whether it is vegetarian, and a user's password hash, which Chapter 21 shows added.

The worked project's model

The team drew one diagram more than MU asks for: a state machine diagram of an order's status, because FR-15's rule, which status may follow which, is exactly what a state machine shows. It kept every diagram as PlantUML source in its repository, under docs/uml:

FileDiagramWhat it showsChapter
use-case.pumluse casethree actors and thirteen use cases20
class.pumlclassUser, Session, Order, OrderItem, MenuItem and three enumerations21
sequence-request.pumlsequencea student places an order: the page and the server22
sequence-transaction.pumlsequencethe same order inside the server: the transaction that takes the stock22
activity-place-order.pumlactivityordering, from the student's side, in swimlanes23
activity-serve-order.pumlactivityserving, from the counter's side23
order-states.pumlstate machinean order's six statuses and the moves between them23
er.pumlER, crow's foot notationthe five tables, their keys and relationships24
er-chen.pumlER, Chen notationthe same data in Chen's notation24
deployment.pumldeploymentthe phones, the counter's device, the server and what runs on it25

The placing of an order was first drawn as one sequence diagram. At 666 pixels wide it could not be read on a phone without zooming, and it was a squeeze on a report page, so the team split it where the page stops and the server starts. Two readable diagrams are better than one that has to be zoomed.

Tools

You can draw UML with a pencil, with a drawing program, with a dedicated modelling tool, or from text. For thinking, a pencil is best. For the document you hand in, choose a tool that lets you change a diagram easily, because you will, many times.

The worked team chose PlantUML, a free, open-source program that draws diagrams from a short text description. Its advantages for a student team:

munotes.in115

System Modeling with UML: the Six Diagrams and How They Fit Together

  • The source is text, so it lives in the Git repository beside the code, and a change to a diagram shows up in the history like any other change.
  • Anyone can redraw it. The same source and the same PlantUML version draw the same picture on any machine.
  • It can be reviewed. A teammate can read the change to class.puml in a pull request, which nobody can do with a screenshot.

Every diagram in this book is drawn by PlantUML from source that the chapter prints, so that you can change it for your own project and draw it again.

To use it you need a Java runtime and the plantuml.jar file from the PlantUML website. From the project folder, this one command draws every diagram in docs/uml as an SVG file beside its source:

java -jar plantuml.jar -tsvg docs/uml/*.puml

Run with PlantUML 1.2026.8 on 30 September 2026, it redrew every UML figure in this book exactly, byte for byte, on a machine with nothing installed but Java. For some kinds of diagram PlantUML normally uses a separate layout program, Graphviz; the team's sources of those kinds begin with !pragma layout smetana, which tells PlantUML to use a layout engine built into it instead.

PlantUML's automatic layout has a price: it decides where the boxes go. A diagram that grows too large becomes unreadable, and the cure is the one the team used for the sequence diagram: split it, rather than shrink it.

Do this for your project

  1. For each of MU's six diagrams, write the one question it must answer about your system.
  2. Draw the use case diagram first, from your functional requirements; then the class diagram from the nouns the use cases use.
  3. Draw a sequence or activity diagram for each important use case, using those classes.
  4. Draw the ER diagram from the classes whose data must be stored, and the deployment diagram from your architecture and hosting plan.
  5. Keep every diagram's source in your repository, in a folder of its own such as docs/uml.
  6. Choose your names once and use them everywhere; write down your one mapping rule between code and database.
  7. Run the seven checks before your design review. Expect them to find something.
  8. Keep each diagram readable on one page. If it is not, split it.

Mistakes that cost marks

Diagrams copied from a textbook example. An examiner recognises a library management system in a canteen project's class diagram.

Six diagrams that disagree. A class with no table, a table with no class, an actor who appears in no requirement: each is a question in the viva.

munotes.in116

System Modeling with UML: the Six Diagrams and How They Fit Together

A class diagram that is really an ER diagram. Foreign keys as attributes and no operations mean the class diagram describes tables, not objects; and an ER diagram with methods in it has the opposite problem.

One enormous diagram. If it needs zooming, it is two diagrams.

Diagrams drawn after the code. MU asks for the UML set at the end of Module 1, before building. A design drawn afterwards has never been used, and it usually shows.

A picture with no source. A screenshot cannot be changed when the design changes, so it stops being true.

Quick revision

  • A model answers questions about a system before it is built; every diagram leaves most of the system out on purpose.
  • UML is a standard modelling language maintained by the OMG; first formal version 1.1 (December 1997), latest 2.5.1 (December 2017).
  • Fourteen diagram kinds in two families: seven structure (class, object, package, component, composite structure, deployment, profile) and seven behaviour (use case, activity, state machine; and the interaction diagrams: sequence, communication, interaction overview, timing).
  • MU's six: use case, class, sequence, activity, deployment, and the ER diagram, which is not UML but comes from Chen (1976).
  • The six are views of one model and must agree: requirements, actors, scenarios, operations, stored data, deployed artifacts and names.
  • Keep diagram sources in the repository; split a diagram rather than shrink it.

Questions you must be able to answer

1. What is UML, and who maintains it? The Unified Modeling Language, a standard visual language for modelling software systems, maintained by the Object Management Group. Its latest formal version is 2.5.1, published in December 2017.

2. What are the two families of UML diagrams? Give two examples of each. Structure diagrams, which show the static parts of a system and how they relate, such as the class diagram and the deployment diagram; and behaviour diagrams, which show what happens over time, such as the use case, activity and sequence diagrams.

3. Is the ER diagram a UML diagram? No. It comes from database design: Peter Chen published the entity-relationship model in 1976. It is used alongside UML because it describes the database's tables, keys and relationships in the database's own terms.

4. How is a class diagram different from an ER diagram? A class diagram describes the objects the program works with, with their attributes, operations and relationships; an ER diagram describes the stored data, with entities, keys and the cardinality of relationships. They must agree on what is stored, but they answer different questions.

5. Name three checks that show a set of UML diagrams describes one system. Every functional requirement is covered by a use case; every class whose data must be kept appears in the ER diagram with the same attributes; and every message in a sequence diagram is an operation or route that the receiving part really has. The same thing must also have the same name in every diagram.

munotes.in117

System Modeling with UML: the Six Diagrams and How They Fit Together

6. Why did the worked team keep its diagrams as PlantUML source? Because text lives in the Git repository with the code, so every change to a diagram is recorded and can be reviewed, and anyone with the same PlantUML version can redraw exactly the same picture when the design changes.

Contents This chapter on its own page

munotes.in118

Chapter Twenty

The Use Case Diagram

Syllabus topic Module 1, "System Modeling using UML: Use Case Diagram".

In one line

A use case diagram shows on one page who uses the system, what they use it for, where the system ends, and how its use cases relate to each other: the functional requirements drawn as a picture, with actors on the outside and use cases on the inside.

In the wording to use when asked: a use case diagram is a UML behaviour diagram showing the actors, the use cases of a subject enclosed by the system boundary, the associations between actors and use cases, and the include, extend and generalisation relationships; each use case is a unit of functionality that yields an observable result of value to an actor or another stakeholder.

The elements, and how each is drawn

ElementWhat it meansHow UML draws it
Actora role played by someone or something outside the system that interacts with ita stick figure with the name below; or a rectangle with the keyword «actor»
Use caseone goal of an actor, which the system deliversan ellipse with the name inside or below it
Subject, or system boundarythe system the use cases belong toa rectangle with the system's name at the top left; use cases inside, actors outside
Associationthis actor takes part in this use casea solid line between them
Includethe base use case always performs the included onea dashed arrow from the base use case to the included one, labelled «include»
Extendthe extending use case adds optional behaviour to the extended one, at a named point, under a conditiona dashed arrow from the extending use case to the extended one, labelled «extend»
Extension pointa named place in a use case where an extension may add its behaviourlisted under the heading "extension points" inside the extended use case's ellipse
Generalisationone actor, or one use case, is a more specific kind of anothera solid line with a hollow triangle pointing at the more general one

The rest of this section explains the four that students most often get wrong.

An actor is a role, not a person

The UML specification describes an actor as a role played by something that interacts with the system: a human user, a piece of hardware or another system. Two consequences follow. Name actors after roles: "Counter staff", never "Ganesh". And one person may play several roles: if Ganesh also orders his own lunch through the system, he is a Student while he does it.

An actor is always outside the system. The system itself is never an actor, and neither is its own database: the database is part of the system.

munotes.in119

The Use Case Diagram

A use case is a goal, not a screen or a step

The specification says a use case yields "an observable result that is of value for Actors or other stakeholders". Chapter 12 turned that into two tests: can the actor see the result, and would they call it worth the visit? "Place an order" passes. "Click Submit", "Validate quantity" and "Home page" do not: they are steps and screens, not goals.

Include and extend, and the direction of the arrow

Both are dashed arrows with a keyword, and students reverse them more than anything else in UML. The specification settles it with one idea: the arrow points from the use case that depends to the use case it depends on.

  • Include. The base use case cannot be complete without the included one, which always runs as part of it. So the base depends on the included use case, and the arrow runs from the base to the included. Use it when two or more use cases share the same steps. The specification's own example is a cash machine, where "Withdraw" includes "Card Identification"; a "Check balance" use case would include it too.
  • Extend. The extended use case is complete and meaningful on its own; the extension only adds behaviour to it, at an extension point, when a condition holds. So the extension depends on the base, and the arrow runs from the extension to the base.

The extended use case lists its extension points inside its ellipse, under the heading "extension points", each as a name with an optional explanation. The condition of the extend may be shown in a note attached to the arrow; the specification makes that note optional.

Lines, not flow

A use case diagram is not a flowchart. It says nothing about the order in which use cases happen. The specification goes further: two use cases of the same system cannot be joined by a plain association at all, because each describes a complete use of the system. Between use cases there are only three relationships: include, extend and generalisation.

The worked diagram

The worked team drew its diagram from Chapter 12's thirteen use cases:

A use case diagram: the actors Student, Owner and Counter staff on the left, the Owner joined to the Counter staff by a generalisation line with a hollow triangle, and thirteen use cases in ellipses inside a rectangle labelled Canteen Pre-order; the use case View my orders lists the extension point cancel, and Cancel an order extends it

Figure 20.1 The worked team's use case diagram, drawn by PlantUML from the source below

@startuml use-case
!pragma layout smetana
left to right direction
skinparam actorStyle awesome

actor Student
actor "Counter staff" as Staff
actor Owner
Owner -|> Staff

rectangle "Canteen Pre-order" {
  usecase "Register" as UC1
  usecase "Sign in / sign out" as UC2
  usecase "Browse today's menu" as UC3
  usecase "Place an order" as UC4
  usecase UC5 as "View my orders
--
extension points
cancel: an order
is still placed"
  usecase "Cancel an order" as UC6
  usecase "View orders by slot" as UC7
  usecase "Update order status" as UC8
  usecase "View kitchen list" as UC9
  usecase "Manage the menu" as UC10
  usecase "Set today's stock" as UC11
  usecase "View daily report" as UC12
  usecase "Create staff account" as UC13
}

Student -- UC1
Student -- UC2
Student -- UC3
Student -- UC4
Student -- UC5
UC6 .> UC5 : <<extend>>
Staff -- UC2
Staff -- UC7
Staff -- UC8
Staff -- UC9
Owner -- UC10
Owner -- UC11
Owner -- UC12
Owner -- UC13
@enduml
munotes.in120

The Use Case Diagram

Reading it

Three actors. Student, Counter staff and Owner, the user classes of Chapter 5. All three are human, and there are no system actors: the one outside system a canteen might have, a payment service, is out of scope in this release (constraint C-5).

The owner is a kind of counter staff. The hollow triangle from Owner to Counter staff is a generalisation: the owner can take part in every use case the counter staff can, signing in, viewing orders by slot, updating an order's status and viewing the kitchen list, as well as in the four that are hers alone. The diagram does not draw those four lines again for the owner; the triangle says it.

Thirteen use cases, UC-1 to UC-13, each realising at least one functional requirement (Chapter 12's table).

One extend. Cancel an order extends View my orders. A student cancels only from the list of their own orders, and only while an order is still placed, so cancelling is optional behaviour added to viewing, at one point, under one condition. View my orders names that point in its ellipse: cancel: an order is still placed. The student has no line of their own to Cancel an order, because they reach it only through the use case it extends.

No include. Most of the use cases need the user to be signed in, and many textbooks would draw «include» from each of them to Sign in. The team did not, for the reason Chapter 12 gave: signing in is a goal of its own, UC-2, and a precondition of the others, not a shared fragment of them.

Who is not an actor. The kitchen staff read the kitchen list every day, but no cook signs in: the counter staff open the list on the counter's screen. The kitchen is a stakeholder that benefits from UC-9, not an actor that interacts with the system, which is why the specification's phrase is "of value for Actors or other stakeholders". And FR-4 shows the menu to everyone, signed in or not. The team drew only the Student against Browse today's menu: the people who look at the menu are students deciding what to eat, and a role is held before signing in as well as after.

munotes.in121

The Use Case Diagram

The source, line by line

  • actor Student draws an actor. actor "Counter staff" as Staff gives a name with a space a short alias to use in the lines below.
  • Owner -|> Staff is the generalisation, with the triangle at the Staff end.
  • rectangle "Canteen Pre-order" { ... } is the system boundary; everything declared inside it is drawn inside it.
  • usecase "Register" as UC1 is a use case. UC5 is written the other way round, with its text in quotes after as, so that its text can run over several lines: the line -- draws the divider, and the lines after it are the extension points compartment.
  • Student -- UC1 is an association: a plain line.
  • UC6 .> UC5 : <<extend>> is the extend: a dashed arrow from UC6 to UC5, labelled.
  • left to right direction puts the actors on the left, and skinparam actorStyle awesome draws each actor as a head and shoulders rather than a stick figure, which the specification allows: other icons may denote an actor.

Checking it

The diagram passes the first two of Chapter 19's checks. Every one of the seventeen functional requirements is realised by a use case, and every use case realises at least one, so the diagram is a complete index of the SRS's functional requirements (Chapter 34). And every actor is a user class of the SRS.

It also passes a check of its own: every actor has at least one association, and every use case has an actor, directly, through the owner's generalisation, or, for Cancel an order, through the use case it extends. A use case nobody can reach is a requirement nobody asked for.

Do this for your project

  1. Draw the boundary first and name it after your system.
  2. Put your actors outside it: roles from your stakeholder register, and any outside system your system talks to.
  3. Put one ellipse inside for each use case from your use-case analysis, named verb first.
  4. Join each actor to the use cases whose goals are theirs.
  5. Draw a generalisation where one actor can do everything another can, and more.
  6. Add include only for steps two or more use cases really share, and extend only for optional behaviour at a named point; list that extension point in the extended use case.
  7. Check the arrows' direction: from the use case that depends to the one it depends on.
  8. Check that every requirement has a use case, every use case has a requirement, and every use case can be reached by an actor.

Mistakes examiners circle

Include and extend reversed. The most common single error in student use case diagrams. Say the rule aloud before drawing each arrow.

munotes.in122

The Use Case Diagram

«include» from every use case to Log in. Signing in is a precondition, or a use case of its own; it is rarely a shared fragment.

Use cases that are screens or steps. "Home page", "Click Submit", "Validate input".

Lines between use cases with no keyword, showing the order in which things happen. A use case diagram has no flow.

The system, or its database, drawn as an actor. Actors are outside the boundary; the database is inside.

Actors named after people. "Lata" is a person; "Owner" is a role.

Actors inside the boundary, or no boundary at all.

Forty use cases on one page. Split the diagram by actor or by part of the system.

Quick revision

  • Actor: a role outside the system (human, hardware or another system); stick figure or «actor» rectangle.
  • Use case: a goal with an observable result of value; an ellipse inside the system boundary rectangle.
  • Association: a solid line between an actor and a use case. No plain lines between use cases.
  • Include: dashed arrow from the base to the included use case, which always runs.
  • Extend: dashed arrow from the extension to the extended use case, optional, at an extension point, under a condition.
  • The rule: the arrow points from the use case that depends to the one it depends on.
  • Generalisation: hollow triangle pointing at the general actor or use case.

Questions you must be able to answer

1. What does a use case diagram show? The actors outside the system, the use cases inside its boundary, which actors take part in which use cases, and the include, extend and generalisation relationships between use cases and between actors. It shows what the system does for whom, not the order in which things happen.

2. Which way does the arrow go for include, and for extend? Why? For include, from the base use case to the included one; for extend, from the extending use case to the extended one. In both cases the arrow points from the use case that depends to the one it depends on: a base needs what it includes, while an extended use case is complete without its extension.

3. What is an extension point? A named place in a use case at which an extending use case may add its behaviour. It is listed inside the extended use case's ellipse under the heading "extension points", with an optional explanation.

4. Why is the owner drawn with a generalisation arrow to the counter staff? Because the owner can take part in every use case the counter staff can, as well as her own. The generalisation says so once, instead of repeating four associations.

munotes.in123

The Use Case Diagram

5. The kitchen staff use the kitchen list. Why are they not an actor? Because they do not interact with the system: no cook signs in, and the counter staff open the list for them. They are stakeholders who benefit from the use case, and UML's definition of a use case allows its value to go to stakeholders other than its actors.

6. Why is there no «include» to Sign in? Because signing in is a goal in its own right and a precondition of the other use cases, not a fragment of behaviour they share. Drawing an include to it from every use case that needs it adds an arrow to most of the diagram and no information.

Contents This chapter on its own page

munotes.in124

Chapter Twenty-One

The Class Diagram

Syllabus topic Module 1, "System Modeling using UML: ... Class Diagram".

In one line

A class diagram shows the kinds of thing a system knows about, what each one holds, what it can do, and how they are related: the system's vocabulary, drawn as boxes and lines, true at every moment rather than describing any one event.

In the wording to use when asked: a class diagram is a UML structure diagram that shows classes with their attributes and operations, and the relationships between them: associations with their multiplicities and navigability, aggregation and composition, generalisation and dependency. It describes the static structure of a system, irrespective of time.

A class, drawn

A class is a rectangle with up to three compartments: its name, its attributes and its operations. Any compartment may be left out when it has nothing useful to say.

An attribute is written in UML's own grammar:

visibility / name : type [multiplicity] = default {modifiers}

Only the name is required. - stockLeft : int = 0 is a private attribute called stockLeft, an integer, starting at zero. A slash before the name marks a derived attribute, one that can be calculated from others. Where no multiplicity is written, an attribute holds exactly one value.

An operation is written visibility name(parameters) : return type, such as + takeStock(n : int) : boolean.

Visibility has four marks:

MarkVisibilityWho can use it
+publicanything that can see the class
-privateonly the class itself
#protectedthe class and its subclasses
~packageanything in the same package

An enumeration is a class of named values and nothing else, drawn with the keyword «enumeration» and its values listed, such as an order's status.

Relationships

RelationshipWhat it meansHow it is drawn
Associationobjects of one class are linked to objects of anothera solid line, optionally named, with a multiplicity at each end
Navigabilityfrom one end you can reach the otheran open arrowhead at the end that can be reached; a small x where it cannot
Aggregationa loose whole and part; the precise meaning is left to the modellera hollow diamond at the whole's end
Compositionthe part belongs to at most one whole at a time, and is deleted with ita filled diamond at the whole's end
Generalisationone class is a more specific kind of another, and inherits from ita solid line with a hollow triangle pointing at the general class
Dependencyone class uses another, so a change to the second may affect the firsta dashed arrow from the user to the used

Multiplicity, the part examiners ask about

A multiplicity at the end of an association says how many objects can be at that end: 1 exactly one, 0..1 none or one, 0.. or any number, 1..* at least one, 1..10 between one and ten.

munotes.in125

The Class Diagram

Read it from one class, across the line, to the number at the far end. On a line between User and Order with 1 at the User end and 0..* at the Order end: a user places any number of orders, and an order is placed by exactly one user.

One rule from the specification is worth knowing: where a diagram shows no multiplicity at the end of an association, no conclusion may be drawn about it. Absent is not the same as "one", so write them all.

Aggregation or composition

The test is two questions about the part: can it exist without the whole, and can it belong to two wholes at once? If both answers are no, it is composition. The specification's own definition: a composite part is included in at most one composite at a time, and if the composite is deleted, its parts are deleted with it.

Aggregation, the hollow diamond, is weaker, and the specification deliberately leaves its exact meaning to whoever uses it. Many teams therefore avoid it, and draw either a plain association or a composition. That is not a mistake; a hollow diamond whose meaning nobody can state is.

Domain model and design model

A class diagram can describe two different things, and a student should say which one theirs is.

A domain model describes the things of the problem itself: orders, menu items, users. It shows their attributes, their associations, and at most the few operations that carry the important rules. It says nothing about files, frameworks or the language.

A design model describes the classes or modules of the code: every operation with its parameters, visibility, the types of the language, often classes that exist only in the program, such as a controller or a data store.

MU's syllabus places the class diagram in the design phase, before building, so a student's class diagram is usually a domain model with its key operations. The worked team's is exactly that.

The worked diagram

A class diagram: User, composed of any number of Sessions by a filled diamond and joined to any number of Orders; Order composed of one to ten OrderItems by a filled diamond; each OrderItem pointing to one MenuItem; and three enumerations, Role, Category and Status, in the left column

Figure 21.1 The worked team's class diagram, drawn by PlantUML from the source below

@startuml class
!pragma layout smetana
top to bottom direction
hide empty members
hide circle

class User {
  id : int
  name : string
  email : string
  passwordHash : string
  role : Role
  isActive : boolean
}

class Session {
  id : string
  expiresAt : datetime
}

class Order {
  id : int
  pickupDate : date
  slot : string
  status : Status
  /totalPaise : int
  canMoveTo(to, by) : boolean
}

class OrderItem {
  quantity : int
  unitPricePaise : int
}

class MenuItem {
  id : int
  name : string
  category : Category
  pricePaise : int
  isVeg : boolean
  isAvailable : boolean
  stockLeft : int
  takeStock(n) : boolean
}

enum Role <<enumeration>> {
  student
  staff
  owner
}

enum Category <<enumeration>> {
  meals
  snacks
  drinks
  desserts
}

enum Status <<enumeration>> {
  placed
  preparing
  ready
  collected
  cancelled
  no_show
}

User "1" *-- "0..*" Session
User "1" -- "0..*" Order : places
Order "1" *-- "1..10" OrderItem
OrderItem "0..*" --> "1" MenuItem
Session -[hidden]down- Role
Role -[hidden]down- Category
Category -[hidden]down- Status
@enduml
munotes.in126

The Class Diagram

Reading it

User 1 to 0..* Session, a composition. A user may be signed in on several devices at once, a phone and a laptop, each a session. Each session belongs to exactly one user and cannot outlive them, so the diamond is filled; the database carries that out by deleting a user's sessions with the user (Chapter 24).

User 1 to 0..* Order, "places". Any number of orders, each placed by one user.

Order 1 to 1..10 OrderItem, a composition. An order item cannot exist without its order and belongs to only one, so the diamond is filled. The 1..10 comes from FR-7: at most 10 items in one order, so at most 10 lines. The rest of FR-7, no more than 5 of any one item and no more than 10 items in all, is a rule about quantities that no multiplicity can express; the SRS states it, and the code checks it.

OrderItem 0..* to 1 MenuItem, navigable one way. Each order item refers to exactly one menu item, and the arrow says an order item knows its menu item while a menu item keeps no list of the orders it is in. It is not a composition: the biryani exists whether or not anyone orders it.

Three enumerations. Role, Category and Status, each with exactly the values the database allows.

No generalisation. The use case diagram made the owner a specialisation of the counter staff. The class diagram does not make Owner a subclass of User. Students, counter staff and owners hold exactly the same data, a name, an email and so on, and differ only in what they may do. A difference in permissions is a value, role, not a new class. A subclass is for a kind of thing that holds different data or behaves differently.

Two operations. canMoveTo on Order and takeStock on MenuItem carry the two rules the whole system depends on: which status may follow which (FR-15), and never selling food that is not there (FR-9). Everything else is data.

One derived attribute. /totalPaise can be calculated from the order's items. The team still stores it, and stores each item's unit price in OrderItem although MenuItem has a price too, for the same reason: a price changed tomorrow must not change the total of an order placed today.

munotes.in127

The Class Diagram

The source, line by line

  • class User { ... } declares a class and lists its attributes; a line with brackets, such as canMoveTo(to, by) : boolean, is an operation.
  • enum Role <<enumeration>> { ... } declares an enumeration and shows UML's keyword above its name.
  • hide empty members leaves out a compartment with nothing in it, as the specification allows, and hide circle removes the small lettered circle PlantUML otherwise puts in every box, which is PlantUML's own decoration and not UML.
  • User "1" -- "0..*" Order : places is an association, with each multiplicity in quotes beside its class and the association's name after the colon.
  • Order "1" -- "1..10" OrderItem is a composition: the draws the filled diamond at the Order end. User "1" -- "0.." Session is another.
  • OrderItem "0..*" --> "1" MenuItem is an association navigable towards MenuItem.
  • The -[hidden]- lines draw nothing. They only ask the layout to stack the three enumerations in a column, which keeps the diagram narrow enough for a phone.

What a class diagram means for a JavaScript backend

The worked application has no JavaScript class called Order. Its backend reads rows from MySQL and hands them on as plain objects, and it keeps the rules in modules of functions. That is normal for a Node.js application, and it does not make the class diagram wrong: the diagram describes what the system knows and what it can do, and the code shows where each piece went when the team built it (Chapters 42 to 45).

An order, as the application sees it, has exactly the fields the store selects, each translated from its column name:

const ORDER_COLUMNS = `o.id, o.user_id AS userId,
  u.name AS studentName, o.pickup_date AS pickupDate,
  TIME_FORMAT(o.pickup_slot, '%H:%i') AS slot, o.status,
  o.total_paise AS totalPaise, o.created_at AS createdAt,
  o.updated_at AS updatedAt`;

Every translation follows the camelCase rule, except one: the column pickup_slot is read as slot. That is the one name that breaks the rule, and Chapter 19's advice applies to it: it is written down, and it is translated in this one place and nowhere else.

The two operations are two functions. Order.canMoveTo(to, by) is canMove(from, to, role), where from is the order's current status:

// Who may move an order from one status to another. The
// owner can do anything the counter staff can.
function canMove(from, to, role) {
  const mover = MOVES[from] && MOVES[from][to];
  if (!mover) return false;
  if (mover === 'student') return role === 'student';
  return role === 'staff' || role === 'owner';
}
munotes.in128

The Class Diagram

MenuItem.takeStock(n) is the store's takeStock, a single SQL statement that takes the stock only if enough is left:

    // Takes `quantity` from the stock only if that many are
    // left, in ONE statement, so two orders for the last
    // plate cannot both succeed. True if it was taken.
    async takeStock(conn, id, quantity) {
      const [result] = await conn.execute(
        `UPDATE menu_items
            SET stock_left = stock_left - ?
          WHERE id = ? AND is_available = TRUE
            AND stock_left >= ?`,
        [quantity, id, quantity]);
      return result.affectedRows === 1;
    },

And the derived total is calculated once, when the order is placed:

// lines: [{ quantity, pricePaise }]. Integers, so exact.
function orderTotal(lines) {
  return lines.reduce(
    (sum, line) => sum + line.quantity * line.pricePaise, 0);
}

If your project is written in Java, C# or PHP with classes, the mapping is even more direct: each class in the diagram becomes a class in the code, and each operation a method.

Checking it against the other diagrams

  • Against the ER diagram (Chapter 24). Every attribute of every class is a column of its table, and every association is a foreign key: userId in Order is the association to User, which is why the class diagram does not list it as an attribute. The one renamed column, pickup_slot, is explained above.
  • Against the sequence diagrams (Chapter 22). Every message sent to part of the system is an operation or a function it really has: taking the stock is takeStock.
  • Against the state machine (Chapter 23). The six values of Status are the six states, and canMoveTo allows exactly its five arrows.

Do this for your project

  1. Underline the nouns in your requirements and use cases; the important ones are your candidate classes.
  2. For each class, list what it must remember: those are its attributes, each with a type.
  3. Add only the operations that carry a business rule.
  4. Draw the associations, and write a multiplicity at both ends of every one. Read each aloud across the line.
  5. Choose composition only where a part cannot exist without its whole; avoid the hollow diamond unless you can say what it means.
  6. Use an enumeration for a fixed set of values, and a subclass only for a kind of thing that holds different data.
  7. Check every attribute against your database design, and write down any name that breaks your mapping rule.

Mistakes that cost marks

Foreign keys as attributes. userId : int inside Order says the same thing as the line to User, twice, and in the database's words rather than the model's.

Missing multiplicities. A line with no numbers says nothing about how many.

Multiplicities read the wrong way round. Read from one class across the line to the far end.

munotes.in129

The Class Diagram

Composition everywhere. A menu item is not part of an order; it exists without one.

A class for every screen or table, such as MenuPage or OrderForm, in a domain model.

Subclasses for roles, when the only difference between the "subclasses" is what they are allowed to do.

Operations with no rule behind them, such as a getter and a setter for every attribute. They add ink and no information.

Quick revision

  • A class: name, attributes, operations. Attribute: visibility / name : type [multiplicity] = default.
  • Visibility: + public, - private, # protected, ~ package. A slash marks a derived attribute.
  • Association with a multiplicity at each end, read across the line; an open arrowhead marks navigability.
  • Composition (filled diamond): the part is in at most one whole and dies with it. Aggregation (hollow diamond): looser, meaning left to the modeller.
  • Generalisation: hollow triangle to the general class. Dependency: dashed arrow from the user to the used.
  • A domain model describes the problem's things; a design model describes the code's classes.

Questions you must be able to answer

1. What does a class diagram show? The classes of a system, with their attributes and operations, and the relationships between them: associations with multiplicities, aggregation and composition, generalisation and dependency. It shows structure that is true at every moment, not what happens in any one scenario.

2. Explain the multiplicities on the association between User and Order in the worked diagram. There is a 1 at the User end and 0..* at the Order end. Reading across the line, a user places any number of orders, including none, and every order is placed by exactly one user.

3. What is the difference between aggregation and composition? Which is used between Order and OrderItem, and why? In composition the part belongs to at most one whole at a time and is deleted with it; aggregation is a looser whole-and-part whose exact meaning UML leaves to the modeller. Order and OrderItem use composition, because an order item cannot exist without its order and never belongs to two.

4. Why is there no Owner subclass of User? Because owners, counter staff and students hold the same data and differ only in what they are permitted to do. A difference in permission is represented by the value of the role attribute, an enumeration; a subclass would be needed only if an owner held different data or behaved differently as an object.

5. Why does OrderItem store a unit price when MenuItem already has a price? Because prices change. The unit price is copied into the order item when the order is placed, so that an order keeps the price the student agreed to, whatever the menu says later. The order's total is stored for the same reason, although it could be derived.

munotes.in130

The Class Diagram

6. The worked backend has no Order class. Is its class diagram still correct? Yes. The diagram is a domain model of what the system knows and does; the code keeps the data in plain objects and the rules in functions, and each operation in the diagram corresponds to a function in the code, such as canMoveTo to canMove and takeStock to the store's takeStock.

Contents This chapter on its own page

munotes.in131

Chapter Twenty-Two

The Sequence Diagram

Syllabus topic Module 1, "System Modeling using UML: ... Sequence Diagram".

In one line

A sequence diagram shows one scenario as a conversation over time: the participants across the top, time running down the page, each message an arrow from the one who sends it to the one who receives it, and frames around the parts that are chosen, optional, repeated or cut short.

In the wording to use when asked: a sequence diagram is a UML interaction diagram that shows the messages exchanged between lifelines, arranged in time order from top to bottom; combined fragments such as alt, opt, loop and break express alternatives, options, iteration and exceptional exits, each with a guard in square brackets.

The parts

PartWhat it meansHow UML draws it
Lifelineone participant in the scenario: an actor, an object, a component, a databasea rectangle with its name, and a dashed line running down the page
Synchronous messagea call whose sender waits for the answera solid line with a filled arrowhead
Asynchronous messagea message whose sender does not waita solid line with an open arrowhead
Replythe answer to a calla dashed line with an arrowhead
Creation messagea message that creates a participanta dashed line with an open arrowhead, ending at the new participant's head
Self-messagea participant doing something itselfan arrow that leaves a lifeline and comes back to it
Activationthe time a participant spends doing somethinga thin rectangle on its lifeline
Combined fragmenta part of the scenario with a rule attacheda rectangle, its operator in a small pentagon at the top left, its parts divided by dashed lines, each part's guard in square brackets

Time runs down the page. The specification requires every message line to run horizontally or downwards from sender to receiver. A message never points up, because it cannot arrive before it was sent.

Name a message after what the receiver can do: an operation of its class, a route of the API, an SQL statement. That is Chapter 19's fourth check, and it is what makes the diagram testable against the code.

The fragments

alt, alternatives. Several parts, each with a guard; at most one runs, the one whose guard is true. A guard of [else] means "none of the others". An if-else.

opt, an option. One part, which either runs or does not, depending on its guard. An if with no else.

loop, repetition. One part, repeated. Its guard can give the least and the most number of times, written loop(1, 10), and a condition to stop.

break, an early exit. When its guard is true, its part runs instead of the rest of the fragment that encloses it, and that rest is skipped. It is how a refusal is drawn: the order is refused, and nothing after it happens. Put the break at the level whose remainder must be skipped; for a refusal of the whole request, that is the level of the whole diagram.

munotes.in132

The Sequence Diagram

There are others. par shows parts that run at the same time, in any interleaving. critical marks a region that must not be interleaved with anything else. A frame labelled ref stands for a whole other sequence diagram, drawn elsewhere, which keeps a large scenario readable.

Drawing one: a scenario at a time

A sequence diagram shows one scenario. Not the system; not even a whole use case with every branch. Choose the scenario first:

  1. Pick a use case, and write its main success scenario and its important extensions (Chapter 12).
  2. Choose the level. Between the user, the page and the server, the participants are what the user sees. Inside the server, they are the parts of the code. Mixing the two in one diagram gives a diagram too wide to read.
  3. Put the participants left to right, in the order in which they first act.
  4. Write the messages top to bottom, each named after what its receiver can do.
  5. Add a frame for each extension: break for a refusal, alt for a real choice, opt for something that may or may not happen, loop for repetition.
  6. Check every message against the class diagram and the API.

The worked diagrams

The worked team drew UC-4, Place an order, at both levels.

The page and the server

A sequence diagram with three lifelines, Student, Menu page and API: the student places an order, the page posts it to the API, and the API checks the session and role, then the input, then places the order; two break frames return 401 or 403 and 400, and an alt frame returns 201 with the order or 409 with the reason

Figure 22.1 UC-4 Place an order, between the student, the page and the server

@startuml sequence-request
!pragma layout smetana
autonumber
hide footbox
actor Student
participant "Menu page" as Page
participant "API" as Api

Student -> Page : Place order
Page -> Api : POST /api/orders
Api -> Api : check session\nand role
break not a signed-in student
  Api --> Page : 401 or 403
  Page --> Student : the message
end
Api -> Api : check input
break input wrong
  Api --> Page : 400 and\nthe problems
  Page --> Student : each problem\nby its field
end
Api -> Api : place order
alt accepted
  Api --> Page : 201 and the order
  Page --> Student : its number
else refused
  Api --> Page : 409 and the reason
  Page --> Student : the reason
end
@enduml

Read it top to bottom. The student taps Place order (1), and the page sends POST /api/orders (2). The API then does three things in turn, each drawn as a self-message, and each can stop the request:

  • Checks the session and the role (3). If the student is not signed in, the answer is 401; if the person signed in is not a student, 403. The first break frame: nothing after it happens.
  • Checks the input (6). A slot that does not exist, or an item listed twice, and the answer is 400 with each problem. The page shows a problem beside its field where the form has one, and the rest in the message at the top.
  • Places the order (9). Either it is accepted, 201 with the order, and the page shows its number; or it is refused, 409 with the reason, such as "Only 2 left". An alt, because both are ordinary outcomes of placing an order.
munotes.in133

The Sequence Diagram

The status codes are the application's own (Chapter 48 explains each). Every message the page sends is a route of the API, and every answer is one the page handles.

Inside the server: the transaction

Message 9, "place order", is where the rules of FR-7 to FR-11 are enforced. The second diagram opens it up:

A sequence diagram with three lifelines, API, Order service and MySQL: the service checks the slot, begins a transaction, locks the student's row and counts their orders; a loop takes the stock of each item in id order and, in an opt frame, reads its price; three break frames end the scenario early with slot_closed, slot_taken, or sold_out or item_unavailable; otherwise the service inserts the order and its lines, commits and returns the new order

Figure 22.2 Message 9 opened up: placing an order inside one database transaction

@startuml sequence-transaction
!pragma layout smetana
autonumber
hide footbox
participant "API" as Api
participant "Order\nservice" as Service
database "MySQL" as Db

Api -> Service : place(user, order)
Service -> Service : is the slot\nstill open?
break slot closed
  Service --> Api : slot_closed
end
Service -> Db : BEGIN
Service -> Db : lock the\nstudent's row
Service -> Db : count their orders\nfor this slot
Db --> Service : the count
break count is not 0
  Service -> Db : ROLLBACK
  Service --> Api : slot_taken
end
loop each item in id order,\nuntil one is short
  Service -> Db : take stock only\nif enough is left
  Db --> Service : 1 row changed, or 0
  opt 1 row changed
    Service -> Db : read its price
  end
end
break an item was short
  Service -> Db : read what is left
  Service -> Db : ROLLBACK
  Service --> Api : sold_out or\nitem_unavailable
end
Service -> Db : INSERT the order\nand its lines
Service -> Db : COMMIT
Service --> Api : the new order
@enduml
  • Is the slot still open? (2) Each slot closes 15 minutes before its time (FR-8). If it has closed, the service answers slot_closed at once, before touching the database.
  • BEGIN (4). Everything that follows, up to COMMIT or ROLLBACK, happens as one transaction: all of it or none of it (Chapter 45).
  • Lock the student's row, then count their orders for this slot (5 to 7). FR-10 allows one active order per student per slot. The lock means that if the same student taps Place order twice in the same instant, the second request waits until the first has finished, and its count then sees the first order. If the count is not zero, the second break: roll back and answer slot_taken. The project's tests send two orders from one student at the same moment and check that exactly one is accepted.
  • The loop (10 to 12). For each item, in the order of its id, the service takes the stock only if enough is left, in one statement, and the database answers how many rows it changed: 1 if the stock was taken, 0 if not. Only when it was taken does the service read the item's price, in the opt frame. Taking the items in id order is deliberate: two orders that share items always take them in the same order, so they can never wait for each other in a circle.
  • An item was short (13 to 15). The third break. The service reads what is left, so that the student can be told "Only 2 left" rather than just "No", rolls back everything taken so far, and answers sold_out, or item_unavailable if the owner has switched the item off.
  • Otherwise (16 to 18), insert the order and its lines, commit, and return the new order.
munotes.in134

The Sequence Diagram

This is the diagram that answers the question every examiner asks of an ordering system: what happens when two students order the last biryani at the same moment? Both run message 10 at once. The database lets only one of them change the row while enough is left: that student's update changes 1 row, the other's changes 0, and the second student is told the biryani is sold out. Chapter 45 proves it with a test in which twenty students order the last five plates at the same moment, and exactly five are served.

What the first drawing got wrong

The team's first version drew the shortage as an alt inside the loop: one part for "taken", one for "short", with the rollback in the second. After the alt the diagram carried on to INSERT and COMMIT, so read top to bottom it said that the order was saved even after it had been rolled back. It also had no refusal for a second order in the same slot, although FR-10 forbids one. Both mistakes survived the design review. When the ordering code was built, in the first increment, a reviewer who compared the diagram with it found both, which is exactly what Chapter 19's checks are for, and the corrected diagrams became version 1.1 of the UML set (Chapter 35).

The fix was to draw each refusal as a break at the level of the whole diagram, so that everything after it is visibly skipped, and to make the loop stop at the first short item, as the code does. Every message was then checked against a function in the code: isSlotOpen, lockUser, countActive, takeStock, the menu store's findById, refusal and insert.

munotes.in135

The Sequence Diagram

What the diagrams leave out

The replies to BEGIN, the lock, INSERT and COMMIT are not drawn; each diagram draws a reply only where its content matters to the story, such as the count or the rows changed. Activation bars are left out for the same reason. Both are choices of readability, not of UML: if your guide expects every reply and every activation, draw them.

The source, line by line

  • actor Student and participant "Menu page" as Page declare the lifelines, left to right in the order written; database "MySQL" as Db draws the database with a cylinder head.
  • Page -> Api : POST /api/orders is a synchronous message; Api --> Page : 201 and the order is a reply, dashed.
  • Api -> Api : check input is a self-message.
  • break slot closed ... end, alt accepted ... else refused ... end, loop ... end and opt ... end draw the fragments; the text after the operator becomes the guard.
  • autonumber numbers the messages, which is what lets the text above say "message 9", and hide footbox stops PlantUML repeating the participants' names at the bottom.

Mistakes examiners circle

One diagram for the whole system. A sequence diagram is one scenario; draw several.

An alt where a break is meant. If the scenario carries on after a failure, the diagram says the failure did not matter.

Arrows that point up, or messages drawn out of order.

Messages nobody can receive: "save order" sent to a class with no such operation, or to the database as if it had methods.

Two levels in one diagram: a tap on a phone and an SQL statement among eight participants.

Guards missing from the fragments, so the reader cannot tell which part runs when.

Do this for your project

  1. Draw a sequence diagram for each use case whose steps matter: at least the one your project exists for.
  2. Draw it at the level of the user and the server first; open up the server in a second diagram where the rules live.
  3. Name every message after a route, an operation or a statement your system really has.
  4. Draw every refusal the code makes, each as a break, so that nothing after it appears to happen.
  5. Compare the diagram with the code line by line once the code exists, and fix whichever is wrong.

Quick revision

  • Lifelines across the top; time runs down; messages are horizontal or downward.
  • Synchronous: filled arrowhead. Asynchronous: open arrowhead. Reply: dashed line. Creation: dashed, to the new lifeline's head.
  • Activation: a thin rectangle on the lifeline.
  • alt (choose one), opt (run or not), loop (repeat; loop(min, max)), break (run instead of the rest); guards in [square brackets], [else] for none of the others.
  • Also par (in parallel), critical (not interleaved), ref (another diagram).
  • One scenario per diagram, at one level.
munotes.in136

The Sequence Diagram

Questions you must be able to answer

1. What does a sequence diagram show? The messages exchanged between the participants of one scenario, in time order from top to bottom: who calls whom, in what order, and what comes back, with frames for the parts that are chosen, optional, repeated or cut short.

2. Distinguish synchronous and asynchronous messages and replies in UML's notation. A synchronous message, whose sender waits, is a solid line with a filled arrowhead; an asynchronous message, whose sender does not wait, is a solid line with an open arrowhead; a reply is a dashed line.

3. What is the difference between alt, opt and break? Alt chooses at most one of several parts, the one whose guard is true. Opt runs its single part or does not. Break runs its part instead of the rest of the enclosing fragment, which is skipped, and is used for an early exit such as a refusal.

4. What happens, in the worked diagram, when two students order the last plate at the same moment? Both send the conditional update that takes stock only if enough is left. The database lets only one of them change the row: that update changes one row and the order goes on; the other changes none, and that student's order is rolled back and refused as sold out.

5. Why does the order service take items in order of their id? So that two orders that share items always take them in the same order. Each then waits for the other at most once, in one direction, and they can never deadlock waiting for each other in a circle.

6. What was wrong with the worked team's first sequence diagram, and how was it fixed? It drew a stock shortage as an alt inside the loop and then continued to insert and commit the order, so the diagram showed an order saved after a rollback; and it had no refusal for a second order in the same slot. Each refusal was redrawn as a break at the level of the whole diagram, and every message was checked against the code.

Contents This chapter on its own page

munotes.in137

Chapter Twenty-Three

The Activity Diagram

Syllabus topic Module 1, "System Modeling using UML: ... Activity Diagram".

In one line

An activity diagram shows how a process flows: actions in rounded boxes, arrows between them in the order they happen, diamonds where the flow chooses a path or comes back together, bars where it splits into parallel paths or waits for them, and swimlanes that show who does each action.

In the wording to use when asked: an activity diagram is a UML behaviour diagram that models the flow of control between actions, with an initial node and final nodes, decision and merge nodes with guarded flows, fork and join nodes for concurrent flows, and activity partitions, drawn as swimlanes, that assign each action to the participant who performs it.

The parts

PartWhat it meansHow UML draws it
Actionone step of the processa rectangle with rounded corners, named with a verb
Control flowwhat happens nextan arrow from one node to the next
Initial nodewhere the process startsa small solid circle
Activity final nodethe end of the whole process: every flow in it stopsa solid circle inside a hollow one, a bull's eye
Flow final nodethe end of one flow only; any others carry ona circle with an X in it
Decision nodethe flow takes exactly one of several pathsa diamond with one arrow in and several out, each out-arrow labelled with a guard in square brackets
Merge nodeseveral paths come back togethera diamond with several arrows in and one out
Fork nodethe flow splits into paths that run at the same timea bar with one arrow in and several out
Join nodethe flow waits until all its incoming paths have arriveda bar with several arrows in and one out
Partition, or swimlanewho performs the actions inside ita column, or a row, headed with the participant's name

Decision and merge, fork and join

The two pairs look alike and mean opposite things.

A decision chooses one path, and a merge takes whichever path arrives. Between them, only one of the paths ever runs. Both are diamonds.

A fork starts all of its paths at once, and a join waits for all of them to finish before the flow goes on. Between them, every path runs. Both are bars.

So if an order had to be both packed and billed before it was handed over, and the two could be done at the same time by different people, that would be a fork and a join: the order is handed over only when both are done. When the student either collects the order or does not, that is a decision.

munotes.in138

The Activity Diagram

Swimlanes, and who may be in them

A swimlane says who does each action. Unlike a use case diagram, which shows only actors, the people who work the system, an activity diagram may give a lane to anyone in the process, including people who never touch the software. That is often the point of drawing one: it shows where the system fits into how the work is really done.

An activity diagram is not just a flowchart

It looks like one, and for a simple process it is read like one. UML adds three things a flowchart does not have: lanes that assign the work, forks and joins for work done in parallel, and a precise meaning for the two kinds of ending, which the specification defines: an activity final node stops every flow in the activity, a flow final node only its own.

Drawing one

  1. Choose one process, from a use case or from the organisation's own work: "an order from the student's side", "serving an order".
  2. List who takes part, and give each a lane.
  3. Write the actions in order, each a verb phrase, in the lane of whoever does it.
  4. Put every choice in a decision diamond in the lane of whoever makes it, and label every outgoing arrow with its guard.
  5. Bring alternative paths back together with a merge, and parallel paths with a join.
  6. End with an activity final node.
  7. Check each action against the requirements and, where the system does it, against the code.

The worked diagrams

Ordering, from the student's side

An activity diagram with two swimlanes, Student and System: the student chooses food and a slot and taps Place order; the system checks the slot and earlier orders and takes the stock all or nothing; at the decision Refused?, yes leads through Say why back to the student's choice, and no leads to Save the order with its number, after which the student notes the number and the activity ends

Figure 23.1 Placing an order, from the student's side

@startuml activity-place-order
!pragma layout smetana
|Student|
start
repeat
  :Choose food
  and a slot;
  :Tap Place order;
  |System|
  :Check the slot and
  earlier orders, and
  take the stock, all
  or nothing;
  backward:Say why;
repeat while (Refused?) is (yes)
->no;
:Save the order
with its number;
|Student|
:Note the number;
stop
@enduml

The diagram is a loop. The student chooses food and a slot and taps Place order. The system makes one attempt, all or nothing: it checks the slot and the student's earlier orders, and takes the stock of every item, and either all of that succeeds or none of it happens. At the decision "Refused?", the system either says why, and the flow goes back, through the merge at the top, to the student choosing again; or it saves the order with its number, which the student notes.

The decision is in the System lane, because the system decides. The loop is drawn with a merge at the top and the decision at the bottom, which is how an activity diagram draws "repeat until".

Serving, from the counter's side

An activity diagram with three swimlanes, Counter, Kitchen and Student: the counter starts preparing, the kitchen cooks the dishes, the counter marks the order ready; at the decision Student in time?, no leads to Mark not collected, and yes to the student giving the number and paying and the counter marking it collected; the paths merge before the activity ends

Figure 23.2 Serving an order, from the counter's side

munotes.in139

The Activity Diagram

@startuml activity-serve-order
!pragma layout smetana
|Counter|
start
:Start
preparing;
|Kitchen|
:Cook the
dishes;
|Counter|
:Mark ready;
if (Student
in time?) then (no)
  :Mark not
  collected;
else (yes)
  |Student|
  :Give the
  number
  and pay;
  |Counter|
  :Mark
  collected;
endif
stop
@enduml

Three lanes. The counter taps Start preparing; the kitchen cooks the dishes; the counter taps Mark ready. Then the one real decision of serving: does the student come before the break ends? If not, the counter marks the order not collected. If so, the student gives the number and pays, and the counter marks it collected. The two paths meet at a merge, and the activity ends.

The kitchen has a lane, but is not an actor. No cook ever signs in, so the kitchen is not in the use case diagram (Chapter 20). But the kitchen does real work in this process, and the diagram would be false without it.

What the first drawings got wrong

Both activity diagrams were redrawn when the team checked them against the code of the first increment, in version 1.1 of the UML set (Chapter 35).

Check, then take. The first ordering diagram had two actions, "Check the slot and the stock" and then, after the decision, "Take the stock". That describes exactly the mistake the system is designed to avoid: between checking and taking, another student can take the last plate, and two students are both promised it. The code checks and takes in one statement (Chapter 22). The diagram now shows one all-or-nothing action, so it says what the code does.

The decision in the wrong lane. "Accepted?" sat in the Student lane, as if the student decided whether their own order was accepted. It is the system's decision, so it moved to the System lane.

No kitchen. The first serving diagram had only the counter and the student, so the dishes appeared to cook themselves.

When a state machine says it better

An activity diagram follows a process from step to step. Sometimes the question is different: what can happen to one thing over its life? An order is placed, prepared, made ready, collected, or cancelled, or never collected. The rule FR-15 states is not a process; it is a list of which status may follow which, and who may make each move.

That is what a state machine diagram, another UML behaviour diagram, is for:

  • A state is a condition the thing can be in: a rectangle with rounded corners.
  • The initial pseudostate, a small solid circle, points to the first state.
  • A final state, a circle around a small solid circle, is where its life ends.
  • A transition is an arrow from one state to another, labelled in UML's form trigger [guard] / effect: what makes it happen, the condition that must hold, and what the system does as it happens. Any part may be left out.
munotes.in140

The Activity Diagram

The worked state machine

A state machine diagram of an order: from the initial pseudostate, order accepted leads to placed; counter taps Start preparing leads to preparing, and student cancels / stock returned leads to cancelled; counter taps Mark ready leads to ready; from ready, counter taps Collected and paid leads to collected, and counter taps Not collected leads to no_show; cancelled, collected and no_show lead to the final state

Figure 23.3 An order's life: its six states and the five moves between them

@startuml order-states
!pragma layout smetana
hide empty description
[*] --> placed : order accepted
placed --> preparing : counter taps\nStart preparing
placed --> cancelled : student cancels\n/ stock returned
preparing --> ready : counter taps\nMark ready
ready --> collected : counter taps\nCollected and paid
ready --> no_show : counter taps\nNot collected
cancelled --> [*]
collected --> [*]
no_show --> [*]
@enduml

Six states and five transitions between them. Each trigger names the button that fires it on the counter's screen, word for word: Start preparing, Mark ready, Collected and paid, Not collected. Only one transition has an effect: when a student cancels, the stock is returned, so the portions go back on sale. When an order is not collected there is no effect, and that is deliberate: the food was cooked, and it is gone.

The diagram is the code's rule, drawn. The application stores exactly these moves, and who may make each:

// Which status may follow which, and whose move it is.
const MOVES = {
  placed: { preparing: 'counter', cancelled: 'student' },
  preparing: { ready: 'counter' },
  ready: { collected: 'counter', no_show: 'counter' },
};

and decides in one place that only a cancelled order returns its stock:

// A cancelled order gives its food back to the stock. A
// no-show does not: that food was cooked and is gone.
function returnsStock(to) {
  return to === 'cancelled';
}

Every arrow of the state machine is an entry in MOVES, and every entry in MOVES is an arrow. Any other move, such as placed straight to ready, is refused (Chapter 43).

Do this for your project

  1. Draw an activity diagram for each process your system changes: how the work is done with it, including the people who never use it.
  2. Give every participant a lane, and put each decision in the lane of whoever decides.
  3. Never split an operation that must be all or nothing into "check" and "do" in your diagram, or in your code.
  4. Use a fork and a join only for work that really happens at the same time, and a decision and a merge for choices.
  5. If your system has something that moves through statuses, such as an order, a booking or an application, draw its state machine, and make your code's rule table match it exactly.

Mistakes that cost marks

A decision drawn as a fork, or a fork as a decision: bars mean "all paths", diamonds "one path".

munotes.in141

The Activity Diagram

Guards missing from a decision's arrows, so nobody can tell which way the flow goes.

Decisions in the wrong lane. Whoever makes the choice owns the diamond.

Paths that never end, or end in mid-air, with no final node.

A flowchart of the code, with "i = i + 1" as an action. An activity diagram of a process is about the work, in the words of the people who do it.

Statuses drawn as an activity diagram. If the question is "what can happen to this order", draw a state machine.

Quick revision

  • Action: rounded rectangle. Initial node: solid circle. Activity final: bull's eye, stops every flow. Flow final: circle with an X, stops one flow.
  • Decision and merge: diamonds, one path runs. Fork and join: bars, all paths run; a join waits for all of them.
  • Guards in square brackets on a decision's outgoing arrows.
  • Swimlanes show who does each action, and may include people who never use the system.
  • A state machine follows one thing through its life: states (rounded rectangles), transitions labelled trigger [guard] / effect, an initial pseudostate and a final state.

Questions you must be able to answer

1. What does an activity diagram show, and what are swimlanes for? The flow of a process from action to action, with its choices, its parallel paths and its ends. Swimlanes divide the actions by who performs them, so the diagram shows who does what as well as in what order.

2. Distinguish a decision node from a fork node. A decision, a diamond, sends the flow down exactly one of its outgoing paths, the one whose guard is true. A fork, a bar, sends it down all of its outgoing paths at once, to run in parallel; a join then waits for all of them.

3. What is the difference between an activity final node and a flow final node? An activity final node, a bull's eye, ends the whole activity: every flow in it stops. A flow final node, a circle with an X, ends only the flow that reaches it, and any other flows carry on.

4. Why is the kitchen a lane in the serving diagram but not an actor in the use case diagram? Because an actor must interact with the system, and no cook signs in; but the kitchen does real work in the serving process, and an activity diagram describes the process, including people who never use the system.

5. What was wrong with drawing "check the stock" and "take the stock" as two actions? It describes a check-then-act race: between the check and the taking, another student can take the last portion, and both are promised it. The system checks and takes in one indivisible step, and the diagram must show one all-or-nothing action.

munotes.in142

The Activity Diagram

6. When would you draw a state machine diagram instead of an activity diagram? When the question is what can happen to one thing over its life rather than how a process flows: its states, the events that move it between them, the conditions and the effects. The worked order's statuses and the moves allowed between them are a state machine, and the code's rule table matches its arrows exactly.

Contents This chapter on its own page

munotes.in143

Chapter Twenty-Four

The ER Diagram

Syllabus topic Module 1, "System Modeling using UML: ... ER Diagram".

In one line

An ER diagram shows the data a system keeps: the kinds of thing it records, the facts it holds about each, what identifies each one, and how they are related, including how many of one can be related to how many of another. It is the design from which the database's tables are built.

In the wording to use when asked: an entity-relationship diagram models the data of a system as entity types with attributes, each identified by a key, and relationship types between them, each with a cardinality ratio (one-to-one, one-to-many or many-to-many) and participation constraints (total or partial); it is the conceptual or logical basis of the database schema.

Where it comes from

The entity-relationship model was published by Peter Chen in 1976, in the first issue of ACM Transactions on Database Systems, as a way of describing data that did not depend on how any particular database stored it. It is older than UML by twenty-one years and is not part of it, but it remains the usual way to design a relational database, and MU names it beside the UML diagrams (Chapter 19).

The words

Entity. One thing the system records: the student Priya, order 12, the Veg Biryani. An entity type is the kind: User, Order, Menu item. A diagram shows entity types, and people often just call them entities.

Attribute. A fact about an entity: a user's name, an order's status. Attributes come in kinds that exam questions like to ask about:

  • Simple or composite: a price is simple; an address, made of house, street and city, is composite.
  • Single-valued or multivalued: an order has one status; a student with several phone numbers has a multivalued attribute.
  • Stored or derived: a quantity is stored; an order's total can be derived from its items' quantities and prices.

Key. An attribute, or a set of attributes, whose value is different for every entity of the type, so that it identifies one. A type may have several candidate keys: a user is identified by their id, and also by their email address. One is chosen as the primary key; the others remain unique. A key made of more than one attribute is a composite key.

Relationship. An association between entities: a user places an order; an order contains menu items. A relationship may have attributes of its own: how many of an item an order contains belongs to neither the order nor the item, but to the pair.

Cardinality ratio. How many entities of one type can be related to one entity of the other: one-to-one (1:1), one-to-many (1:N) or many-to-many (M:N). One user places many orders; each order is placed by one user: 1:N. One order contains many items, and one item is in many orders: M:N.

munotes.in144

The ER Diagram

Participation. Whether every entity of a type must take part in the relationship. Total: every order is placed by some user. Partial: a user may have placed no orders at all.

Weak entity. An entity that cannot be identified by its own attributes, only together with the entity it depends on. A line of an order, identified by its order and its menu item, is the classic example.

Two notations

Chen's notation

The notation textbooks name after Chen draws:

SymbolMeaning
rectanglean entity type
double rectanglea weak entity type
ellipsean attribute
ellipse with its name underlineda key attribute
double ellipsea multivalued attribute
dashed ellipsea derived attribute
diamonda relationship type
double diamondthe identifying relationship of a weak entity
1, N, M beside the linesthe cardinality ratio
single linepartial participation
double linetotal participation

Chen's notation is good for thinking: every attribute, key and relationship is a separate shape, so the structure of the data is visible at a glance. It is poor for a large design, because the ellipses soon fill the page.

Crow's foot notation

The other common notation draws each entity as a box listing its attributes, and puts the cardinality and participation at the ends of the relationship line:

End of the lineMeans
two bars, one after the otherexactly one
a circle and a barzero or one
a bar and a crow's footone or many
a circle and a crow's footzero or many

The circle means zero, so the participation is optional; the bar means one; the three-pronged crow's foot means many. Read it the way you read a UML multiplicity: from one entity, along the line, to the symbol at the far end. This is the notation of most database design tools, and it fits a design with every column in it.

Resolving a many-to-many relationship

A relational database cannot store a many-to-many relationship directly: a column holds one value, so neither the orders table nor the menu items table can hold "all the items of this order" or "all the orders of this item". The relationship becomes a table of its own, an associative entity:

  1. Make a new entity for the relationship: order_items.
  2. Give it a foreign key to each side: order_id and menu_item_id.
  3. Make the pair its primary key, if each pair may occur only once: one line per item per order.
  4. Move the relationship's own attributes into it: quantity, and the unit_price_paise copied at the moment of ordering.
munotes.in145

The ER Diagram

The one M:N relationship becomes two 1:N relationships: an order has many order items, and a menu item appears in many order items. The line of an order is also exactly the weak entity described above.

Conceptual, logical, physical

The same data can be designed at three levels, and the worked team drew two of them:

  • A conceptual model shows the entities, their relationships and the important attributes, in the language of the problem. It ignores tables and types. Chen's notation suits it.
  • A logical model shows every attribute, every key and every relationship in the form the database will need, with many-to-many relationships resolved. Crow's foot suits it.
  • A physical model is the schema itself: the SQL that builds the tables, with exact types, constraints and indexes (Chapter 29).

The worked diagrams

The conceptual model, in Chen's notation

A Chen diagram: USER with key Id and Name joined through the diamond PLACES to ORDER, 1 to N; ORDER with key Id, Slot and the dashed derived attribute Total joined through the diamond CONTAINS to MENU_ITEM, M to N; CONTAINS has its own attributes Quantity and UnitPrice; MENU_ITEM has key Id and Price; ORDER's two lines are thick for total participation

Figure 24.1 The worked conceptual model in Chen's notation

@startchen er-chen
top to bottom direction
entity USER {
  Id <<key>>
  Name
}
entity ORDER {
  Id <<key>>
  Slot
  Total <<derived>>
}
entity MENU_ITEM {
  Id <<key>>
  Price
}
relationship PLACES {
}
relationship CONTAINS {
  Quantity
  UnitPrice
}
USER -1- PLACES
PLACES =N= ORDER
ORDER =M= CONTAINS
CONTAINS -N- MENU_ITEM
@endchen

Three entities and two relationships. A USER PLACES many ORDERs, 1:N. An ORDER CONTAINS many MENU_ITEMs, and a menu item is in many orders, M:N, with the quantity and the unit price as attributes of CONTAINS, because they belong to the pair.

Keys are underlined: each Id. Total, dashed, is derived: it can be computed from the quantities and unit prices of the order's items.

Participation. ORDER's two lines are drawn thick, because its participation in both relationships is total: every order is placed by a user and contains at least one item. USER and MENU_ITEM take part partially: a user may never order, and an item may never be ordered. The textbook symbol for total participation is a double line. PlantUML draws a thick line instead, from the = in PLACES =N= ORDER; say so beside your diagram if you draw it this way, since an examiner may look for the double line.

What is left out. The conceptual model shows the canteen's data as the canteen would describe it, so the table of signed-in sessions, which exists only because the application needs it, does not appear, and each entity shows only the attributes that make the relationships clear. Every attribute is in the logical model below.

The logical model, in crow's foot notation

A crow's foot diagram of five tables: users, with id as primary key and email marked unique, joined one-to-zero-or-many to sessions and to orders; orders joined one-to-one-or-many to order_items, whose primary key is order_id and menu_item_id together, both also foreign keys; order_items joined zero-or-many-to-one to menu_items, whose name is marked unique

Figure 24.2 The worked logical model in crow's foot notation: every column of the schema

@startuml er
!pragma layout smetana
top to bottom direction
hide circle
skinparam linetype ortho

entity users {
  * id : INT <<PK>>
  --
  * name : VARCHAR(80)
  * email : VARCHAR(120) <<unique>>
  * password_hash : VARCHAR(200)
  * role : ENUM
  * is_active : BOOLEAN
  * created_at : DATETIME
}

entity sessions {
  * id : CHAR(64) <<PK>>
  --
  * user_id : INT <<FK>>
  * created_at : DATETIME
  * expires_at : DATETIME
}

entity orders {
  * id : INT <<PK>>
  --
  * user_id : INT <<FK>>
  * pickup_date : DATE
  * pickup_slot : TIME
  * status : ENUM
  * total_paise : INT
  * created_at : DATETIME
  * updated_at : DATETIME
}

entity order_items {
  * order_id : INT <<PK, FK>>
  * menu_item_id : INT <<PK, FK>>
  --
  * quantity : TINYINT
  * unit_price_paise : INT
}

entity menu_items {
  * id : INT <<PK>>
  --
  * name : VARCHAR(60) <<unique>>
  * category : ENUM
  * price_paise : INT
  * is_veg : BOOLEAN
  * is_available : BOOLEAN
  * stock_left : INT
}

users ||--o{ sessions
users ||--o{ orders
orders ||--|{ order_items
order_items }o--|| menu_items
@enduml
munotes.in146

The ER Diagram

Five tables, every column of the schema, and the M:N relationship already resolved into order_items. Read each relationship both ways, from one table along the line to the far end:

  • users to sessions: a user has zero or many sessions; a session belongs to exactly one user.
  • users to orders: a user has zero or many orders; an order belongs to exactly one user.
  • orders to order_items: an order has one or many items, never none; an item line belongs to exactly one order.
  • order_items to menu_items: a menu item appears in zero or many item lines; each line is for exactly one menu item.

Inside the boxes, PlantUML's * before a column marks it mandatory: it must have a value, which is NOT NULL in SQL, and every column here is. «PK» and «FK» mark the primary and foreign keys; order_items has a composite primary key made of its two foreign keys, which is why each item can appear only once in an order. «unique» marks the other candidate keys: no two users share an email, and no two menu items share a name.

The source, line by line

  • entity users { ... } draws an entity box; -- inside it draws the line that separates the key from the other columns.
  • * id : INT <<PK>> is a mandatory column with a keyword.
  • users ||--o{ sessions is a relationship: || at the users end means exactly one, o{ at the sessions end means zero or many. |{ would be one or many, and o| zero or one.
  • hide circle removes PlantUML's own lettered circle from each box, and skinparam linetype ortho draws the lines with right angles.
  • In the Chen source, entity, relationship, <<key>> and <<derived>> draw the shapes, and -1-, =N= put the cardinality on each line, = for total participation.
munotes.in147

The ER Diagram

Checking it against the class diagram

Chapter 19's fifth check, class against ER, and one of Chapter 21's rules, meet here:

Class diagram (Chapter 21)ER diagramHow they correspond
User, Session, Order, OrderItem, MenuItemusers, sessions, orders, order_items, menu_itemsone table per class
attributes in camelCasecolumns in snake_casepickupDate is pickup_date, and so on for every attribute but one
Order's slotpickup_slotthe one exception to the rule, translated in one place in the code (Chapter 21)
an association linea foreign key columnuser_id in orders is the line from Order to User
composition, Order and OrderItemON DELETE CASCADE on order_itemsan order's items are deleted with it
composition, User and SessionON DELETE CASCADE on sessionsa user's sessions are deleted with them
no created or updated timescreated_at, updated_atbookkeeping columns the database keeps for every row; the class diagram, a domain model, leaves them out

The check runs from the class diagram to the ER diagram: every attribute of every class must be stored. The reverse need not hold, and the table shows the one kind of column that is stored without being in the domain model.

The composition rows are worth noticing. The association from Order to User has no ON DELETE CASCADE, and that is deliberate: a user's orders are the canteen's records of what it sold, so the application never deletes a user. An account is switched off instead, by setting is_active to false, which sign-in and every session check honour. The first release has no screen for that; it is done in the database.

Do this for your project

  1. List the entities from your class diagram's classes, and give each a primary key.
  2. List each entity's attributes; mark composite, multivalued and derived ones, and every candidate key.
  3. Draw each relationship, and give it a cardinality ratio and a participation at both ends.
  4. Resolve every many-to-many relationship into an associative entity, with its own attributes.
  5. Draw a conceptual model in Chen's notation for your report, and a logical model with every column in crow's foot notation for building the database.
  6. Check it against your class diagram in both directions, and write down every difference with its reason.

Mistakes that cost marks

A many-to-many relationship left unresolved in a logical model: it cannot become a table.

A relationship's attributes placed in an entity, such as quantity in the menu item.

No primary key, or a name used as the key when two things can share a name.

munotes.in148

The ER Diagram

Cardinality written at the wrong end. Read from one entity across the line to the far end.

Participation missing. "Every order has at least one item" is a rule the database should enforce, and it starts in the diagram.

Foreign keys without the line, or lines without a foreign key: the diagram and the tables must say the same thing.

Quick revision

  • Entity type (rectangle), attribute (ellipse), relationship (diamond), in Chen's notation; key underlined, multivalued double ellipse, derived dashed ellipse, weak entity double rectangle.
  • Keys: candidate, primary, unique, composite, foreign.
  • Cardinality ratio: 1:1, 1:N, M:N. Participation: total (every entity takes part; double line) or partial.
  • Crow's foot: bar = one, circle = zero, crow's foot = many; read from one entity to the far end.
  • Resolve M:N into an associative entity with a foreign key to each side and the relationship's attributes.
  • Conceptual (Chen), logical (crow's foot, every column), physical (the SQL schema).

Questions you must be able to answer

1. What does an ER diagram show? The entity types a system stores data about, their attributes and keys, and the relationships between them, with each relationship's cardinality ratio and participation. It is the design from which the database tables are built.

2. Distinguish cardinality from participation, with an example of each from the worked diagram. Cardinality says how many entities of one type can relate to one of the other: a user places many orders, and each order is placed by one user, 1:N. Participation says whether every entity must take part: every order must be placed by some user, total; a user need not place any order, partial.

3. How is a many-to-many relationship turned into tables? Use orders and menu items. It becomes an associative table, order_items, with a foreign key to each side, order_id and menu_item_id, which together form its primary key, and with the relationship's own attributes, the quantity and the unit price at the time of ordering. The M:N relationship becomes two 1:N relationships.

4. What is a derived attribute? Give the worked example. An attribute whose value can be computed from others. An order's total is the sum of its items' quantities multiplied by their unit prices; Chen's notation draws it as a dashed ellipse.

5. What is a weak entity? An entity that cannot be identified by its own attributes alone, only together with the entity it depends on. An order's line is identified by its order and its menu item together, and has no identity without the order.

6. Why are the canteen's users never deleted from the database? Because their orders are the canteen's records of what it sold, and deleting a user would mean deleting or orphaning those orders. So the orders table does not cascade deletes from users, the application has no way to delete one, and an account is switched off instead by setting is_active to false, which sign-in and every session check honour.

Contents This chapter on its own page

munotes.in149

Chapter Twenty-Five

The Deployment Diagram

Syllabus topic Module 1, "System Modeling using UML: ... Deployment Diagram".

In one line

A deployment diagram shows where the software runs: the machines, the software environments on them, the files deployed into those environments, and the network connections between the machines, each labelled with what travels on it.

In the wording to use when asked: a deployment diagram is a UML structure diagram that shows the physical architecture of a system: nodes, which may be devices or execution environments, the artifacts deployed on them, and the communication paths between the nodes.

The parts

PartWhat it meansHow UML draws it
Nodeanything software can be deployed ona three-dimensional box, which the specification calls a perspective view of a cube
Devicea physical machine: a phone, a laptop, a servera node with the keyword «device»
Execution environmentsoftware that runs other software: an operating system, a web server, a runtime, a database servera node with the keyword «executionEnvironment»; one may be nested inside another
Artifacta physical piece of the system that is deployed: an executable, a script, an app package, a database's tablesa rectangle with the keyword «artifact», or with a document icon
Deploymentthis artifact runs on that nodethe artifact drawn inside the node, or a dashed arrow labelled «deploy»
Communication paththe two nodes exchange messagesa line between them, labelled with the protocol and the port

The specification's own examples of execution environments include an operating system and a database system, and it notes that one environment may run inside another: a database server inside an operating system.

What it is for

A deployment diagram answers the questions a class diagram cannot: what must be installed where, which machine talks to which, on what port, and what can be reached from the network. Those answers decide how the system is hosted (Chapters 57 to 60) and much of how it is secured (Chapter 31): every port a diagram shows open to the network is a door someone can knock on.

The worked diagram

A deployment diagram: two devices at the top, Counter device and Student's phone, which holds the artifact canteen.apk; both joined by HTTP port 80 paths to Nginx inside the device Lab desktop; inside the lab desktop an execution environment Ubuntu 24.04 holds three more: Nginx, Node.js 24 holding the artifact canteen-preorder, and MySQL 8.0 holding the artifact canteen database; Nginx is joined to the application on 127.0.0.1 port 3000, and the application to the database on 127.0.0.1 port 3306

Figure 25.1 Where the worked system runs during the trial on the college network

@startuml deployment
!pragma layout smetana
node "Student's phone" <<device>> as Phone {
  artifact "canteen.apk" as Apk
}
node "Counter device" <<device>> as Counter
node "Lab desktop" <<device>> as Server {
  node "Ubuntu 24.04" <<executionEnvironment>> as Os {
    node "Nginx" <<executionEnvironment>> as Nginx
    node "Node.js 24" <<executionEnvironment>> as Node {
      artifact "canteen-preorder" as App
    }
    node "MySQL 8.0" <<executionEnvironment>> as Db {
      artifact "canteen\ndatabase" as Data
    }
  }
}
Phone --> Nginx : HTTP 80, from\na browser or the app
Counter --> Nginx : HTTP 80, from\na browser
Nginx --> App : HTTP\n127.0.0.1:3000
App --> Data : TCP\n127.0.0.1:3306
@enduml

Reading it

Three devices. The student's phone, which reaches the system from a browser or from the canteen's Android app, canteen.apk (Chapter 59). The counter's device, a tablet or a laptop with a browser; during the trial, the owner's phone, until the tablet is bought (assumption A-4). And the lab desktop, the old machine the IT lab in-charge offered and reinstalled with Ubuntu 24.04 (Chapter 6).

munotes.in150

The Deployment Diagram

Environments inside environments. Inside the lab desktop runs Ubuntu 24.04, and inside Ubuntu three more execution environments: Nginx, a web server; Node.js 24, the runtime that executes the application; and MySQL 8.0, the database server.

Three artifacts. canteen-preorder, the application, deployed in Node.js; the canteen database's tables, deployed in MySQL; and canteen.apk, deployed on the phones.

Four communication paths, and what travels on each:

  • Phone and counter to Nginx: HTTP on port 80, across the college Wi-Fi. This is the only door the system opens to the network.
  • Nginx to the application: HTTP to 127.0.0.1, port 3000. The address 127.0.0.1 means "this machine": the connection never leaves the lab desktop. Nginx receives every request and hands it on.
  • The application to MySQL: TCP to 127.0.0.1, port 3306, also inside the machine.

So neither the application nor the database can be reached from the network at all. The application listens on 127.0.0.1 unless told otherwise, which is its own default, and Ubuntu's MySQL listens on 127.0.0.1 in the configuration Ubuntu itself ships. Of the system's own software, only Nginx faces the network, and it is where HTTPS will be added (Chapter 60).

UML draws a communication path as a plain line. The worked diagram adds an arrowhead to show which side opens the connection, the phone to Nginx and never the other way, which is a convention worth stating beside your own diagram.

HTTP on the college network, and when HTTPS comes

The paths from the phones say HTTP, not HTTPS, and that is deliberate and honest. HTTPS needs a certificate that students' phones trust, and such certificates are issued for a public name, or a public address, that the issuer can check belongs to whoever asks. The lab desktop has neither: the IT in-charge would not make it reachable from the internet (Chapter 6).

Two things follow. The sign-in cookie is not marked "secure only", which NFR-4 allows, because it asks for that only when the site uses HTTPS (Chapter 11). And what travels between phones and the server across the college Wi-Fi, passwords included, is not encrypted by the application. That is the trial's largest security weakness. Chapter 31 records it with its fix: a public server with a name and HTTPS (Chapters 58 and 60), which also lifts the other limit of the trial, that ordering works only on the college Wi-Fi.

munotes.in151

The Deployment Diagram

A diagram that claimed HTTPS here would be tidier and false. The worked team's first version did say HTTPS 443, and it was corrected when the team asked where the certificate would come from.

The source, line by line

  • node "Lab desktop" <<device>> as Server { ... } draws a node with its keyword, and everything declared inside the braces is drawn inside it.
  • node "Nginx" <<executionEnvironment>> as Nginx is an execution environment, nested in Ubuntu's.
  • artifact "canteen-preorder" as App is an artifact, drawn with a document icon.
  • Phone --> Nginx : HTTP 80, from\na browser or the app is a communication path with its label; \n breaks the label over two lines.

Checking it

Chapter 19's sixth check passes. Every artifact is something the architecture says will be built: the application, the database the schema creates (Chapter 29), and the APK. Every node is a machine that exists: the lab desktop the IT in-charge offered, the students' phones, and the counter's device, whose purchase is costed in Chapter 7.

Other deployments

The same application can be deployed differently, and each deployment gets a diagram of its own. On a rented cloud server (Chapter 58) the lab desktop becomes a virtual machine in a data centre, the phones reach it across the internet by a name, the path from them carries HTTPS on port 443, and the Wi-Fi limit disappears. The environments and artifacts inside stay the same, which is the reward for keeping the application and the database off the network.

Do this for your project

  1. Draw every machine your system runs on or is used from as a «device».
  2. Inside each, draw the execution environments it needs: operating system, web server, runtime, database server.
  3. Put each of your artifacts inside the environment that runs it.
  4. Draw every network connection, labelled with its protocol and port.
  5. Make sure the only paths open to the network are the ones users must use, and say where HTTPS is, or honestly why it is not yet.
  6. If you will deploy in more than one way, such as the lab and the cloud, draw each.

Mistakes that cost marks

A diagram of components with no machines, which is an architecture diagram, not a deployment diagram.

Paths with no protocol or port. "Connects to" says nothing a reader can check or configure.

A database reachable from the network because nobody asked where it listens.

HTTPS drawn where there is no certificate. An examiner will ask where it comes from.

The user's device left out. The phone is part of the deployment: it is where the pages run.

Quick revision

  • Node: a 3-D box. Device «device»: a physical machine. Execution environment «executionEnvironment»: software that runs software; may be nested.
  • Artifact «artifact» or a document icon: an executable, a package, a script, database tables.
  • Deployment: the artifact inside the node, or a dashed «deploy» arrow.
  • Communication path: a line between nodes, labelled with protocol and port.
  • Show what is open to the network; keep everything else on 127.0.0.1.
munotes.in152

The Deployment Diagram

Questions you must be able to answer

1. What does a deployment diagram show? The physical architecture of a system: the devices and execution environments software is deployed on, the artifacts deployed on each, and the communication paths between the nodes, with their protocols.

2. What is the difference between a device and an execution environment? A device is a physical machine, such as a phone or a server. An execution environment is software on which other software runs, such as an operating system, a web server, a runtime like Node.js or a database server; execution environments run on devices and may be nested inside each other.

3. What is an artifact? Give three from the worked diagram. A physical piece of the system that is deployed, such as an executable, a package or a database's tables. The worked diagram has the application, canteen-preorder, the canteen database's tables, and the Android package, canteen.apk.

4. Why do the application and the database listen on 127.0.0.1? Because 127.0.0.1 means the machine itself, so connections to them can come only from inside the lab desktop. Neither can be reached from the network; the only door the system opens to it is Nginx's port 80.

5. Why does the worked diagram show HTTP and not HTTPS on the paths from the phones? Because HTTPS needs a certificate the phones trust, issued for a public name or address that the issuer can check, and the lab desktop has neither. The trial therefore runs on HTTP within the college network, the weakness is recorded, and HTTPS comes with a public server.

Contents This chapter on its own page

munotes.in153

Chapter Twenty-Six

System Architecture Design: Styles, Layers and Choosing the Stack

Syllabus topic Module 1, "System Architecture Design: Frontend architecture, Backend architecture, Database schema design, API structure, Security considerations", as a whole.

In one line

A system's architecture is its handful of big decisions: what the main parts are, where each runs, how they talk to each other, what they are built with, and why. For a mini project it is almost always a client and a server in three tiers, with the server's code in layers, as one application, built with a stack the team already knows.

In the wording to use when asked: software architecture is the high-level structure of a system: its principal components, their responsibilities and interactions, how they are deployed, and the rationale for the decisions that shaped them. Architecture styles such as client-server, three-tier, layered and MVC are reusable forms for that structure, and each significant decision is recorded, with its context and consequences, in an architecture decision record.

Why it comes before building

An architecture decision is expensive to change. Swapping one library for another costs an afternoon; moving from one database to another, or splitting one application into three, costs weeks. So the decisions that are hard to reverse are made deliberately, early, and written down with their reasons. The rest of Module 1's architecture section, frontend, backend, database, API and security (Chapters 27 to 31), fills in the detail inside the frame this chapter sets.

The styles

Client and server

A client asks; a server answers. The student's phone asks for the menu and the server answers with it. Almost every system you will build for this paper is client-server, because the data must live in one place that every user reaches.

Three tiers

A three-tier architecture separates a system into:

  1. Presentation: what the user sees and touches. The pages in the browser.
  2. Application, or logic: the rules. The server program.
  3. Data: what is kept. The database.

A tier can run on a machine of its own, and each talks only to the tier next to it: the browser never talks to the database directly. That is what lets the database stay on 127.0.0.1, invisible to the network (Chapter 25).

Layers

Inside one tier, the code can be divided into layers, each with one job and each calling only the layer below it. Tiers are about where code runs; layers are about how one program is organised. A common layering of a server:

  • Routes, or controllers: turn an HTTP request into a call, and the result back into a response.
  • Services: the business logic, the use cases themselves.
  • Data access, or a store: every SQL statement, and nothing else.

The payoff is that each question has one place to look. "What does the API do with a bad slot?" is in the routes. "When is an order refused?" is in the services. "What SQL takes the stock?" is in the store.

munotes.in154

System Architecture Design: Styles, Layers and Choosing the Stack

MVC

Model-view-controller divides an application into the model, the data and its rules; the view, what is shown; and the controller, which takes input and decides what to do. Frameworks such as Laravel and Django are built around it (Django calls its version model-view-template). In a server that returns JSON to pages that draw themselves, the view moves into the browser, and the server's routes and services play the controller and the model.

One application or many

A monolith is a single application, deployed as one piece, with one database. Microservices split a system into many small applications, each deployed separately, each with its own data, talking over the network.

Microservices solve problems a mini project does not have: many teams releasing independently, or one part needing a thousand times the capacity of another. They bring problems it cannot afford: every call between parts can now fail, a single order can span several databases, and there are several things to deploy instead of one. A team of four with 60 hours each builds one application, in layers. Chapter 19's advice about diagrams applies to code too: split when there is a reason, not before.

Choosing a stack by criteria

The stack is the set of languages, frameworks, databases and runtimes the system is built with. Choose it by criteria written down in advance, not by what is fashionable this month:

CriterionThe question
FitDoes it do what the requirements need: web pages for phones and laptops, a database, an Android app?
SkillsCan this team build with it now? Chapter 6's skills matrix answers this.
HostingCan it run where the system must run, here an Ubuntu desktop in the college lab?
CostIs everything free for this use?
SupportWill the versions chosen still get security fixes when the project is examined?
ExplainabilityCan every member explain every part in the viva?

The candidates

StackFitSkills (Chapter 6)What changes from this book
PHP with MySQL, plain or with Laravelgood; pages can be drawn on the serverone member had built a small PHP site; none had written PHP for two yearsthe same tiers and layers; Laravel is MVC and brings routing, sessions and database access built in; hosting through a web server running PHP
Django with MySQL or PostgreSQLgood; its built-in admin pages could have served the owner's menu screensall four knew Python, but none had used a Python web frameworkmodel-view-template; the database tables are generated from Python model classes and changed by migrations; the API from an add-on such as Django REST Framework
MERN: MongoDB, Express, React, Node.jsgoodExpress and MongoDB from the MEAN paper; nobody knew React, because that paper's frontend was Angulara document database, where an order and its items can be one document; the stock rule needs MongoDB's own conditional update; the pages become a React application with a build step
Node.js with Express and MySQL, plain HTML, CSS and JavaScriptgoodevery member rated 2 or 3 in every column except the Linux serverthis book
Native Android in Kotlin with Firebasepoor for the counter and the owner, who work on a tablet or a laptoptwo members rated 2an app instead of pages; Firebase's hosted database instead of the team's own server; the counter and the owner still need a web page
munotes.in155

System Architecture Design: Styles, Layers and Choosing the Stack

Every one of these can build the canteen system, and a team that already knows one of the others well should choose it: the tiers, the layers, the diagrams and every check in this book stay the same, and only the code differs.

The worked decision, recorded

An architecture decision record, or ADR, is a short document for one significant decision. The format most teams use comes from a 2011 post by Michael Nygard, which gives it five parts: a title; the context, the forces at play, stated neutrally; the decision, stated in the active voice; the status, such as proposed, accepted or superseded; and the consequences, good and bad. The worked team kept its records in its architecture design document, where Chapter 36 prints them.

ADR-1: Node.js, Express and MySQL, with plain pages and an Android wrapper

Status. Accepted, in the third week, at the feasibility study (Chapter 6).

Context. Students order from their phones; the counter and the owner work on a tablet or a laptop; the owner edits the menu. The system runs on an Ubuntu 24.04 desktop in the college lab, reachable on the college Wi-Fi. There is no money for software or hosting (constraint C-2). Each member has 60 hours for the whole paper. All four built an Express application last semester; nobody has written PHP for two years; nobody knows Python web frameworks; nobody has deployed Node.js on Linux.

Decision. We will build one Node.js application with Express, using MySQL through the mysql2 driver, and write the pages in plain HTML, CSS and JavaScript without a framework. The server will answer the pages with JSON. Students who want an app will get an Android app that opens the same pages. We will use Node.js 24, a long-term support line, MySQL 8.0 as Ubuntu 24.04 installs it, with the same version on our own laptops, and exact versions of our two libraries, Express 5.2.1 and mysql2 3.24.5.

Consequences. One language, JavaScript, runs on the server and in the browser, and every member can read every file. Everything is free. Node.js 24 is supported until 30 April 2028, beyond the examination. MySQL 8.0 left Oracle's own support on 21 April 2026, when Oracle moved it to Sustaining Support; Ubuntu 24.04 still ships security fixes for its 8.0 package, which is why we accept it on the lab desktop, and the application must keep to what later versions such as 8.4 LTS also support, so that the server can move when the college's does. Plain pages need no build step and teach the fundamentals, but give no structure of their own, so we will write shared helpers for requests and page elements and use them everywhere. MySQL gives us transactions for the stock rule, which we must use correctly (Chapter 45). The Android app is a wrapper around the web pages, not a native app: it works only with the server. Nobody has deployed Node.js on Linux: a practice deployment is planned in week 8 (Chapters 6 and 18).

munotes.in156

System Architecture Design: Styles, Layers and Choosing the Stack

Beside ADR-1, the team recorded four smaller decisions, each a record of its own: ADR-2, prices stored as whole paise, never rupees with a decimal point (Chapter 29); ADR-3, sessions kept in the database, with only a hash of each cookie stored (Chapter 31); ADR-4, one clock for the whole application, which the database never overrules (Chapter 43); and ADR-5, taking an order's stock in one conditional update inside a transaction (Chapter 45).

The worked architecture

An architecture diagram: two clients, browser pages and the Android app, send HTTP to the Middleware of the server application; the middleware passes /api requests to Routes and any other path to the static files in public; Routes call Validation and Services; Validation and Services use Rules; Services call the Store, which alone sends SQL to MySQL

Figure 26.1 The worked architecture: three tiers, and the server's code in layers

@startuml architecture
!pragma layout smetana
top to bottom direction
package "Clients" {
  [Browser pages\nHTML, CSS, JS] as Pages
  [Android app\n(WebView)] as Apk
}
package "Server application (Node.js, Express)" {
  [Middleware] as Mw
  [Static files\npublic/] as Static
  [Routes] as Routes
  [Validation] as Validate
  [Services] as Services
  [Rules] as Rules
  [Store\n(all the SQL)] as Store
}
database "MySQL" as Db
Pages --> Mw : HTTP
Apk --> Mw : HTTP
Mw --> Routes : /api/...
Mw --> Static : any other path
Routes --> Validate
Routes --> Services
Validate --> Rules
Services --> Rules
Services --> Store
Store --> Db : SQL
@enduml

Read it from the top:

  • Presentation tier. The browser pages and the Android app, which shows the same pages. Both speak only HTTP, and only to the server.
  • Application tier, one Express application. Every request passes through the middleware first: the request log, the security headers, and for the API, reading JSON and the session. A request for a path under /api goes to the routes; any other path is a file from public/, a page, a stylesheet or a script, which is how the browser gets the pages in the first place.
  • The routes check the input with validation, then call a service. The services apply the canteen's rules, the slots, the moves between statuses, the totals, and ask the store for data. The store holds every SQL statement in the application, and is the only part that talks to the database.
  • Data tier. MySQL, on the same machine, reached only by the store.
munotes.in157

System Architecture Design: Styles, Layers and Choosing the Stack

Each box is a folder or a file of the worked code: src/middleware, src/routes, src/validate.js, src/services, src/rules.js, src/store and public/. The frontend's inside is Chapter 27's subject, the backend's Chapter 28's, the tables Chapter 29's and the API between the tiers Chapter 30's.

Do this for your project

  1. Draw your tiers: what runs in the browser or on the phone, what on the server, what in the database.
  2. Decide the layers inside your server, and which folder each lives in.
  3. Write down your criteria for the stack before choosing it; score your candidates against them, with your skills matrix.
  4. Record the decision as an ADR: title, context, decision, status, consequences, including the bad ones.
  5. Record every other decision that would be expensive to reverse the same way, one short record each.
  6. Draw the architecture diagram from your code's real structure once it exists, and correct it where it differs.

Mistakes that cost marks

A stack chosen because it is popular, which nobody on the team can explain in the viva.

Microservices in a mini project. Three applications to deploy, one database shared among them anyway, and a network call where a function call would do.

No layers: SQL inside the route handlers, rules scattered across files. Every change touches everything.

Decisions without reasons. "We used MySQL" is a fact; the ADR's context and consequences are what earn the marks.

An architecture diagram of intentions. If the code calls validation from the routes, the diagram must say so.

Quick revision

  • Architecture: the main parts, where they run, how they talk, what they are built with, and why.
  • Client-server; three tiers: presentation, application, data; each talks only to the next.
  • Tiers are where code runs; layers organise one program: routes, services, data access.
  • MVC: model, view, controller.
  • Monolith for a mini project; microservices only for problems a mini project does not have.
  • Choose a stack by criteria: fit, skills, hosting, cost, support, explainability.
  • ADR (Nygard, 2011): title, context, decision, status, consequences.

Questions you must be able to answer

1. What is software architecture? The high-level structure of a system: its main parts and what each is responsible for, how they interact, where they run, what they are built with, and the reasons for those decisions.

munotes.in158

System Architecture Design: Styles, Layers and Choosing the Stack

2. Distinguish tiers from layers. Tiers are divisions by where code runs, such as the browser, the server and the database, and each can be on its own machine. Layers are divisions of one program's code by responsibility, such as routes, services and data access, each calling only the one below.

3. Why is a single application better than microservices for a mini project? Because microservices solve problems of scale and of many independent teams that a four-person project does not have, and they add costs it cannot afford: network calls that can fail between parts, data spread across services, and several deployments instead of one.

4. What is an architecture decision record, and what does it contain? A short document recording one significant decision, in the form Michael Nygard proposed in 2011: a title, the context and forces at play, the decision itself, its status, and its consequences, good and bad.

5. Why did the worked team choose Node.js, Express and MySQL? Because every member could already build with them, one language serves both the server and the pages, everything is free, it runs on the Ubuntu desktop in the college lab, and Node.js 24 is supported until April 2028. The one weakness, that nobody had deployed Node.js on Linux, was planned for with a practice deployment in week 8.

6. What would change if a team built the same system with Django? The tiers, layers and diagrams would stay the same. The code would be Python, organised as model-view-template; the tables would be generated from model classes and changed by migrations; the owner's menu screens could use Django's built-in admin; and the JSON API would usually come from an add-on such as Django REST Framework.

Contents This chapter on its own page

munotes.in159

Chapter Twenty-Seven

Frontend Architecture

Syllabus topic Module 1, "System Architecture Design: Frontend architecture".

In one line

A frontend architecture decides what the user sees and how it is built: the screens and how they lead to each other, what each looks like before it is built, how the pages are made and how they talk to the server, where the data they show lives, and how they work on a small phone, for every user, safely.

In the wording to use when asked: frontend architecture is the design of the presentation tier: its screens and navigation, their layout, the choice between server-rendered pages, a multi-page application and a single-page application, the way the client communicates with the server's API, where client state is held, responsive and accessible design, and the client-side defences such as output encoding against cross-site scripting.

The questions it answers

  1. Which screens are there, and how does a user move between them?
  2. What does each screen show, and where? Answered cheaply, before building, with wireframes.
  3. How are the pages built? Drawn on the server, or drawn in the browser; one page or many; with a framework or without.
  4. How do they talk to the server? Through which API, in which format, and what happens when the server says no.
  5. Where does the data live while a page is open? And how does a page find out something changed?
  6. Will it work on a small phone, and for everyone? Responsive and accessible design.
  7. Can anyone make the page run code it should not? Cross-site scripting, the frontend's own security question.

Screens and navigation

List every screen, who may see it, and how one leads to the next. A screen map, sometimes called a navigation diagram, draws it: screens as boxes, the links and actions that move a user between them as arrows. It is quick to draw and it answers the question every guide asks in the first minute of a demonstration: "where does a student start, and where can they go?"

The worked screens

A screen map: from the start, the sign-in and registration page leads to the Menu when a student signs in and to the Counter when counter staff or the owner signs in; Menu and My orders link to each other; Counter and Owner link to each other, the link to Owner shown to the owner only

Figure 27.1 The worked screens and the ways between them

@startuml screens
!pragma layout smetana
hide empty description
state "Sign in, register\nindex.html" as Index
state "Menu\nmenu.html" as Menu
state "My orders\norders.html" as Orders
state "Counter\ncounter.html" as Counter
state "Owner\nowner.html" as Owner
[*] --> Index
Index --> Menu : a student\nsigns in
Index --> Counter : counter staff or\nthe owner signs in
Menu --> Orders : My orders
Orders --> Menu : Menu
Counter --> Owner : Owner,\nowner only
Owner --> Counter : Counter
@enduml
PageWho sees itWhat it is forUse cases
index.htmleveryonesigning in, and registering as a studentUC-1, UC-2
menu.htmlstudentstoday's menu, the order being put together, placing itUC-3, UC-4
orders.htmlstudentstoday's orders and their status; cancellingUC-5, UC-6
counter.htmlcounter staff and the ownertoday's orders by slot, moving each on, the kitchen listUC-7, UC-8, UC-9
owner.htmlthe ownerthe menu, today's stock, the day's report, staff accountsUC-10 to UC-13
munotes.in160

Frontend Architecture

After signing in, a student lands on the menu and everyone else on the counter; a page opened by the wrong role sends its visitor to their own home page. The owner's page is reached from a link that only the owner is shown. Every page a signed-in user sees has a Sign out button, which leads back to the first. Every use case of Chapter 20 has a page, and every page serves at least one use case.

Wireframes

A wireframe is a plain sketch of one screen: its parts and where they go, with no colours, no images and no final wording. It exists to be shown to users and changed before anything is built, when a change costs a minute. Pencil and paper is a perfectly good wireframing tool; the worked team drew theirs in PlantUML's wireframe form, called Salt, so that they could keep them in the repository beside the other diagrams.

The menu, as a student sees it

A wireframe of the menu page: a header with Canteen Pre-order, Menu and My orders; today's menu with two items, Veg Thali and Chicken Biryani, each with minus and plus buttons and a count, the second marked Only 3 left; the student's order with its lines and total; a pickup time choice reading 12:40, order by 12:25; and a Place order button

Figure 27.2 Wireframe: the menu page, drawn before it was built

@startsalt wireframe-menu
{+
  {* Canteen Pre-order | Menu | My orders }
  ==
  <b>Today's menu
  <b>Meals
  {+
    Veg Thali | [ - ] | 2 | [ + ]
    Veg, Rs 70 | . | . | .
  }
  {+
    Chicken Biryani | [ - ] | 0 | [ + ]
    Non-veg, Rs 110 | . | . | .
    Only 3 left | . | . | .
  }
  <b>Your order
  {
    2 x Veg Thali | Rs 140.00
    Total | <b>Rs 140.00
  }
  Pickup time
  ^12:40 (order by 12:25)^
  [ Place order ]
}
@endsalt

The counter

A wireframe of the counter page: a pickup slot choice set to 12:40; order 14, Priya Menon, placed, 2 Veg Thali, with a Start preparing button; order 15, Kabir Singh, ready, 1 Samosa, with Collected and paid and Not collected buttons; and a table of what is still to make for 12:40

Figure 27.3 Wireframe: the counter page, drawn before it was built

@startsalt wireframe-counter
{+
  {* Canteen Pre-order | Counter }
  ==
  <b>Orders at the counter
  Pickup slot
  ^12:40^
  {+
    <b>14: Priya Menon | Placed
    2 x Veg Thali | .
    [ Start preparing ] | .
  }
  {+
    <b>15: Kabir Singh | Ready
    1 x Samosa | .
    [ Collected and paid ] | [ Not collected ]
  }
  <b>Still to make for 12:40
  {#
    Item | Quantity
    Veg Thali | 2
  }
}
@endsalt

The team showed both wireframes to the owner and to Ganesh at the counter before building. The counter one carries the decisions of Chapter 9's interview with Ganesh: one slot at a time, every action a single labelled button, nothing to type. The kitchen list at the bottom came from the head cook's interview (Chapter 5).

munotes.in161

Frontend Architecture

How the pages are built

There are three common ways:

  • Server-rendered pages. The server builds each page's HTML for every request, with the data already in it. PHP and Django's templates work like this.
  • A multi-page application. Each screen is its own HTML page, served as a file; a small script on each page asks the server's API for data and draws it.
  • A single-page application. One HTML page is loaded once, and a larger script, usually built with a framework such as React or Angular, swaps screens inside it.

The worked team built a multi-page application with no framework, for reasons ADR-1 recorded (Chapter 26): nothing to install or compile, every page small enough for one person to understand, and the browser's own links and back button working as users expect. Each page is one HTML file and one script, and every script imports two shared modules:

  • api.js, the only code that talks to the server. It sends and receives JSON, and turns any refusal into one kind of error that every page shows the same way.
  • ui.js, small helpers every page uses: building elements, formatting rupees, showing messages and errors, finding out who is signed in.

This is the answer to ADR-1's warning that plain pages "give no structure of their own": the structure is these two modules, and a rule that no page talks to the server or builds HTML any other way.

Here is one whole page, to show the anatomy every page shares:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Menu | Canteen Pre-order</title>
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="/css/app.css">
  <script type="module" src="/js/menu.js"></script>
</head>
<body>
  <header class="bar">
    <span class="brand">Canteen Pre-order</span>
    <nav>
      <a href="/menu.html" aria-current="page">Menu</a>
      <a href="/orders.html">My orders</a>
    </nav>
    <span class="who" id="who"></span>
    <button type="button" class="link" id="signout">Sign out</button>
  </header>
  <main class="page with-side">
    <section class="main-column">
      <h1>Today's menu</h1>
      <p class="message" id="message" role="alert" hidden></p>
      <div id="menu" aria-live="polite">
        <p>Loading the menu...</p>
      </div>
    </section>
    <form id="order-form" class="card side-column">
      <h2>Your order</h2>
      <p id="cart-empty">Nothing yet. Use the + buttons.</p>
      <ul id="cart-lines" class="lines"></ul>
      <p class="total">Total <strong id="cart-total">₹0.00</strong></p>
      <label for="slot">Pickup time</label>
      <select id="slot" name="slot" required></select>
      <button type="submit" id="place">Place order</button>
      <p class="hint">Pay at the counter when you collect.</p>
    </form>
  </main>
  <a href="#order-form" class="cart-bar" id="cart-bar" hidden></a>
</body>
</html>

The header and the navigation, marked with aria-current for the page the user is on; one message area that screen readers announce; the menu area, which announces changes politely; the order form with a label for every field; and one script, loaded as a module.

Talking to the server

Every request is JSON to a path under /api on the same server that sent the page (Chapter 30). The browser sends the sign-in cookie with each request by itself, and scripts cannot read it (NFR-4), so no page ever handles a password or a session after signing in. When the server refuses, it answers with a status code and a message, which api.js hands to the page to show, beside the field it concerns where the form has one (Chapter 22).

munotes.in162

Frontend Architecture

Where the data lives

The server is the source of truth. A page holds only what it is showing and what the user is in the middle of doing: the cart on the menu page lives in the page until the order is placed, and nowhere else. Everything else is asked for again when needed.

Two pages must notice changes made by someone else, and both ask again on a timer: a student's orders every 15 seconds, as FR-12 requires, and the counter's list every 10 seconds, which Ganesh asked for at the first increment review (Chapter 15). Asking on a timer, called polling, is the simplest way to stay current, and at a canteen's scale it costs nothing that matters. Pushing changes from the server instead needs a connection held open to every phone, which the team did not need.

Building before the server exists

Open any page with ?mock=1 at the end of its address, and api.js sends its requests to mock.js, which answers with made-up data instead. The mock answers the student's side of the API, the menu, the slots and the student's own orders, so Sneha built the menu and the orders pages while Farhan was still building the server, as Chapter 17's plan needed: the frontend and the backend ran side by side from 11 September. The counter's and the owner's pages came later and were built against the real server, whose routes by then answered. The mock follows the API contract of Chapter 30, which is what makes it safe to build against.

Designing for a phone first

Most students will order on a phone, so the stylesheet is written for a narrow screen first, and one rule, applied only at 48rem wide and above, rearranges the same page for a tablet or a laptop, putting the order beside the menu instead of below it. That order matters: a page designed for a laptop and squeezed onto a phone breaks, while a page designed for a phone and given more room simply spreads out.

NFR-7 set the test: every page usable at 320 CSS pixels wide without sideways scrolling, the width WCAG 2.2 uses for reflow. It was measured on every page at authoring, and passed. On a phone the order form is below a long menu, so a bar at the bottom of the screen shows the cart's total and jumps to the form.

munotes.in163

Frontend Architecture

Accessible to everyone

NFR-8 turned WCAG 2.2 into four checks:

  • A visible label for every field, tied to it, so a screen reader says what to type. The page above shows one.
  • Text contrast of at least 4.5 to 1. Measured on every colour pair in the stylesheet at authoring: the weakest, the error text, dark red on white, is 6.47 to 1.
  • Tap targets at least 24 by 24 CSS pixels, the minimum of WCAG 2.2's success criterion 2.5.8. Measured on the built menu page: the smallest, the header links, is exactly 24.
  • Everything works from a keyboard, which is checked with the rest of acceptance testing (Chapter 54).

Beyond those, the pages announce messages to screen readers, mark a field with an error as invalid and point it at its message, and never use colour alone: a vegetarian item says "Veg", in words, as well as in green.

Frontend security: text, never HTML

The owner types menu item names. If a page ever put such a name into the page as HTML, a name like <script>...</script> would run as code in every student's browser: cross-site scripting, one of the most common attacks on web applications. The worked pages have one rule that closes it: every piece of data is added to a page as text, never as HTML. The helper that builds every element says so:

// Makes an element. Text is always set as text, never as
// HTML, so a menu item named "<script>..." is shown as
// those characters and never run (that would be XSS).
export function el(tag, attributes = {}, ...children) {
  const node = document.createElement(tag);
  for (const [name, value] of Object.entries(attributes)) {
    if (name === 'class') node.className = value;
    else if (name.startsWith('on')) {
      node.addEventListener(name.slice(2), value);
    } else if (value === true) node.setAttribute(name, '');
    else if (value !== false && value != null) {
      node.setAttribute(name, value);
    }
  }
  // `false` is skipped as well as null and undefined, so a
  // page can write `ready && el(...)` for an optional part.
  for (const child of children.flat()) {
    if (child == null || child === false) continue;
    node.append(child instanceof Node ? child : String(child));
  }
  return node;
}

node.append with a string adds text; nothing a user typed can become markup. The server adds a second defence, a policy that tells the browser not to run scripts from anywhere but the site's own files (Chapter 31).

Do this for your project

  1. List your screens, who sees each, and which use cases each serves; draw the screen map.
  2. Wireframe the screens that matter most, and show them to the people who will use them before you build.
  3. Choose how the pages are built, and record why.
  4. Put all talk with the server in one module, and all element building in another; let no page do either any other way.
  5. Decide where each piece of data lives; poll where a page must notice other people's changes.
  6. Design for 320 pixels first, and check reflow, contrast, labels, target sizes and the keyboard on the built pages.
  7. Never put user-supplied data into a page as HTML.
munotes.in164

Frontend Architecture

Mistakes that cost marks

Screens nobody can reach, or use cases with no screen.

No wireframes, so the first time the users see the design is when it is too late to change it.

Fetch calls scattered through every page, each handling errors differently.

A desktop design squeezed onto a phone.

Colour as the only signal, such as red and green for vegetarian with no words.

innerHTML with user data, the classic way to let a stranger's script run in every user's browser.

Quick revision

  • Frontend architecture: screens and navigation, wireframes, how pages are built, how they talk to the server, where data lives, phone-first, accessibility, XSS.
  • Server-rendered pages; multi-page application; single-page application.
  • One module for all server talk, one for building elements.
  • Polling: ask again on a timer; the worked pages poll every 15 s (student) and 10 s (counter).
  • Mobile first: style the narrow screen first, then add room with a min-width rule.
  • WCAG 2.2: labels, contrast 4.5 to 1, targets 24 px, keyboard, reflow at 320 px.
  • Text, never HTML, for anything a user typed.

Questions you must be able to answer

1. What does a frontend architecture decide? The screens and the navigation between them, their layouts, how the pages are built and delivered, how they communicate with the server's API, where client-side data is kept and how changes are noticed, how the pages adapt to screen sizes, how they meet accessibility requirements, and how they are protected against cross-site scripting.

2. What is a wireframe, and why draw one? A plain sketch of a screen's parts and their positions, without colours or final wording. It lets the users see and correct the design before anything is built, when a change costs minutes instead of days.

3. Distinguish a multi-page application from a single-page application. In a multi-page application each screen is its own HTML page, loaded when the user goes to it. In a single-page application one page is loaded once, and a script swaps the screens inside it, usually with a framework.

4. How do the worked pages know when an order's status changes? They poll: a student's orders page asks the server again every 15 seconds, as FR-12 requires, and the counter's page every 10 seconds. The server remains the source of truth, and each page shows its latest answer.

munotes.in165

Frontend Architecture

5. What does designing for a phone first mean? Writing the styles for the narrowest screen first and adding rules that use extra room on wider screens, rather than designing for a laptop and squeezing the result onto a phone. The worked stylesheet has one such rule, applied at 48rem and wider.

6. How do the worked pages prevent cross-site scripting? Every piece of data is added to a page as text, never as HTML, through one helper that every page uses, so a menu item's name containing script tags is shown as characters and never run. The server adds a content security policy as a second defence.

Contents This chapter on its own page

munotes.in166

Chapter Twenty-Eight

Backend Architecture

Syllabus topic Module 1, "System Architecture Design: ... Backend architecture".

In one line

A backend architecture decides how the server's code is divided and how a request travels through it: the layers and what each may do, the chain of middleware every request passes, where the settings come from, what is logged, and how it all maps onto folders, so that anyone can find where any behaviour lives.

In the wording to use when asked: backend architecture is the internal design of the application tier: its layers (typically routes or controllers, services and data access) with a rule that each depends only on the layers below it, the middleware pipeline that applies cross-cutting concerns such as logging, security headers, parsing and authentication to every request, configuration supplied from the environment rather than the code, structured logging and error handling, and a folder structure that mirrors the layers.

The layers, and the one rule

Chapter 26 divided the server into layers. The worked backend has five, and one rule: each layer may use only the layers below it.

LayerIts one jobMay useMust never
Routesturn an HTTP request into a call, and the result into a responsevalidation, servicescontain SQL or business rules
Validationcheck that every input is the right shape and within limitsrules, for constants such as the slotsknow about HTTP or the database
Servicescarry out the use cases: place an order, move it on, sign inrules, the store, transactionstouch a request or a response
Rulesthe canteen's rules as plain functions: slots, moves, totals, timenothing at allread the clock, the database or anything outside their arguments
Storeevery SQL statement in the application, and nothing elsethe databaseknow about HTTP or rules

The rule is what keeps a change small. The day the canteen adds a 13:10 slot, one list in the rules changes. The day the database changes, only the store does. A route never needs to know how an order is stored, and the store never needs to know that an order came over HTTP.

The worked team checked their code against the rule, not just their intentions: no route file imports the store, no service touches a request or a response, the store imports neither the rules nor Express, and the rules import nothing. There is one deliberate exception, and it is written down: the health check route asks the database pool directly whether it answers, because testing that connection is its whole job.

The rules are pure functions. A pure function's result depends only on its arguments, and it changes nothing outside itself. canMove(from, to, role) needs no database and no clock to answer. That makes the most important logic in the system the easiest to test: a unit test calls it with arguments and checks the answer (Chapter 51).

munotes.in167

Backend Architecture

Built from parts, not wired to them

The worked backend builds itself from its parts in one function, and every part is given what it needs instead of fetching it: the configuration, the database pool, the store, the clock and the log are passed in. That is called dependency injection, and it is what makes the backend testable. The real server passes the real clock; the tests pass a clock that stands still, their own test database and a log that stays quiet, and exercise exactly the same code.

Here is that function, the whole backend's wiring in one file:

'use strict';

const path = require('node:path');
const express = require('express');
const { attemptLimiter } = require('./attempts');
const { authService } = require('./services/auth');
const { menuService } = require('./services/menu');
const { orderService } = require('./services/orders');
const { securityHeaders, jsonOnly } = require('./middleware/security');
const { loadSession } = require('./middleware/session');
const { requestLog } = require('./middleware/log');
const {
  apiNotFound, pageNotFound, errorHandler,
} = require('./middleware/errors');
const { authRoutes } = require('./routes/auth');
const { menuRoutes } = require('./routes/menu');
const { orderRoutes } = require('./routes/orders');
const { canteenRoutes } = require('./routes/canteen');
const { userRoutes } = require('./routes/users');
const { healthRoutes } = require('./routes/health');

const FIFTEEN_MINUTES = 15 * 60 * 1000;

// "false", "true", "loopback" or a number, as Express wants.
function proxySetting(value) {
  if (value === 'false') return false;
  if (value === 'true') return true;
  return /^\d+$/.test(value) ? Number(value) : value;
}

// Builds the whole application from its parts. Nothing here
// opens a port: server.js does that, and the tests call this
// with their own database, clock and log instead.
function createApp({ config, pool, store,
  clock = () => new Date(), log = console }) {
  const auth = authService({
    store, config, clock,
    attempts: attemptLimiter({ max: 5, windowMs: FIFTEEN_MINUTES }),
  });
  const menu = menuService({ pool, store });
  const orders = orderService({ pool, store, config, clock });

  const app = express();
  app.disable('x-powered-by');
  app.set('trust proxy', proxySetting(config.trustProxy));

  if (config.logRequests) app.use(requestLog(log));
  app.use(securityHeaders(config));
  app.use('/api', express.json({ limit: '10kb' }), jsonOnly,
    loadSession(auth));

  app.use('/api/auth', authRoutes({ auth, config }));
  app.use('/api/menu', menuRoutes({ menu }));
  app.use('/api/orders', orderRoutes({ orders }));
  app.use('/api/users', userRoutes({ auth }));
  app.use('/api/health', healthRoutes({ pool }));
  app.use('/api', canteenRoutes({ orders }));
  app.use('/api', apiNotFound);

  app.use(express.static(path.join(__dirname, '..', 'public')));
  app.use(pageNotFound);
  app.use(errorHandler(log));
  return app;
}

module.exports = { createApp };

The middleware pipeline

Middleware is code that every request passes through on its way to a route, and every response on its way out. It is where the concerns that apply to everything live, so that no route has to repeat them. In Express, middleware runs in the order it is added, and the order matters. Read the app.use lines above in order:

munotes.in168

Backend Architecture

  1. The request log, first, so that it can time the whole request.
  2. Security headers, on every response: the content security policy, no framing by other sites, no guessing file types (Chapter 31).
  3. For paths under /api only: JSON parsing, refusing a body over 10 kilobytes; JSON only, refusing a change sent as anything but JSON, or from a page on another site (Chapter 31); and the session, which works out who is signed in from the cookie.
  4. The routes, one router per area: sign-in, the menu, orders, users, health, and the canteen's own lists.
  5. API not found: any /api path no router answered gets a JSON 404.
  6. Static files: any other path is a file from public/, a page, a stylesheet, a script.
  7. Page not found: a friendly HTML 404 page.
  8. The error handler, last, where every error from every earlier step ends up.

One request, traced

A student places an order, POST /api/orders:

  • The log notes the start time. The security headers are set.
  • The body is parsed as JSON, checked to be JSON from this site, and the session cookie becomes req.user.
  • The orders router's first guard checks that req.user is a student; if not, 401 or 403.
  • The route calls validation on the body; if it is wrong, 400.
  • The route calls the order service's place, which checks the slot with the rules and runs the transaction through the store (Chapter 22). If the service refuses, 409.
  • The route answers 201 with the order. When the response has been sent, the log writes one line.
  • If anything unexpected throws anywhere, the error handler answers 500 with a plain apology.

Configuration from the environment

A setting that differs between a laptop, the lab and a server, such as a database password, a port or whether cookies need HTTPS, must not be written in the code. The worked backend reads every setting from environment variables, in one file, src/config.js, and nowhere else. On startup, server.js loads a file called .env into the environment if there is one; a variable already set in the real environment wins.

.env holds real passwords, so it is never committed: the repository's .gitignore excludes it. What is committed instead is .env.example, every setting with a safe value and a comment saying what it does:

# Copy this file to .env and put in your own values.
# .env is listed in .gitignore: it must never be committed.

# Where the application listens. 127.0.0.1 means this
# computer only; 0.0.0.0 means every network it is on.
PORT=3000
HOST=127.0.0.1

# The MySQL database and the account the application uses.
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=canteen
DB_USER=canteen
DB_PASSWORD=change-this-password

# Only addresses at this domain may register as students.
EMAIL_DOMAIN=college.example

# How long a sign-in lasts, and whether the cookie is sent
# only over HTTPS (true once the site is served over HTTPS).
SESSION_HOURS=8
COOKIE_SECURE=false

# The canteen's clock. The server may be in another zone:
# every time is worked out here and the database only
# stores it.
APP_TIME_ZONE=Asia/Kolkata

# Leave empty. Set it, for example to
# 2026-09-29T10:30:00+05:30, only to demonstrate the site at
# a time of day when every real pickup slot has closed. The
# clock then stands still at that moment.
DEMO_TIME=

# Set to loopback when the application sits behind Nginx.
TRUST_PROXY=false
LOG_REQUESTS=true
munotes.in169

Backend Architecture

Two settings in it matter for the trial deployment of Chapter 25. HOST stays 127.0.0.1, so the application cannot be reached from the network, only through Nginx. And TRUST_PROXY must be set to loopback behind Nginx: otherwise every request appears to come from Nginx on the same machine. The limit on failed sign-ins counts attempts per address and email together (NFR-6), so with one address for everybody, five wrong passwords typed by anyone for a student's email would lock that student out, even on their own phone (Chapter 60).

And this is the file that reads them, the only one in the backend that touches process.env. It answers every question about a setting in one place: what its name is, what it defaults to, what type it becomes, and which ones the application refuses to start without.

'use strict';

// Every setting the application reads, in one place. The
// values come from the environment (server.js loads the .env
// file into it), never from the code: the same code then runs
// on a laptop, in the lab and on a server, and no password is
// ever written into a file that is committed.

function loadConfig(env) {
  const config = {
    port: whole(env.PORT, 3000),
    host: env.HOST || '127.0.0.1',
    db: {
      host: env.DB_HOST || '127.0.0.1',
      port: whole(env.DB_PORT, 3306),
      database: env.DB_NAME || 'canteen',
      user: env.DB_USER || 'canteen',
      password: env.DB_PASSWORD || '',
    },
    emailDomain: (env.EMAIL_DOMAIN || 'college.example')
      .toLowerCase(),
    sessionHours: whole(env.SESSION_HOURS, 8),
    cookieSecure: env.COOKIE_SECURE === 'true',
    timeZone: env.APP_TIME_ZONE || 'Asia/Kolkata',
    demoTime: moment(env.DEMO_TIME),
    trustProxy: env.TRUST_PROXY || 'false',
    logRequests: env.LOG_REQUESTS !== 'false',
  };
  if (config.db.password === '') {
    throw new Error('DB_PASSWORD is not set. '
      + 'Copy .env.example to .env and fill it in.');
  }
  return config;
}

// DEMO_TIME, if set, is a moment such as
// 2026-09-29T10:30:00+05:30 at which the application's clock
// stands still. See .env.example for why it exists.
function moment(value) {
  if (value === undefined || value === '') return null;
  const at = new Date(value);
  if (Number.isNaN(at.getTime())) {
    throw new Error(`DEMO_TIME "${value}" is not a date and time.`);
  }
  return at;
}

function whole(value, fallback) {
  if (value === undefined || value === '') return fallback;
  const n = Number(value);
  if (!Number.isInteger(n)) {
    throw new Error(`Expected a whole number, got "${value}".`);
  }
  return n;
}

module.exports = { loadConfig };
munotes.in170

Backend Architecture

Three habits in it are worth copying. Every value is converted here, so no other file has to remember that COOKIE_SECURE arrives as the string "true". A missing database password stops the application, with a sentence saying what to do, rather than failing later at the first query. And the defaults are the safe ones: 127.0.0.1 rather than every network, false rather than trusting a proxy.

If DB_PASSWORD is missing, the application refuses to start, with a message saying how to fix it. A server that starts and then fails on the first request is harder to diagnose than one that refuses to start at all.

Logging

The backend writes one line for every request, once its response has been sent:

'use strict';

// One line per request once its response has been sent: the
// method, the address, the status code and the time taken.
function requestLog(log) {
  return (req, res, next) => {
    const start = process.hrtime.bigint();
    res.on('finish', () => {
      const ms = Number(process.hrtime.bigint() - start) / 1e6;
      log.info(`${req.method} ${req.originalUrl} `
        + `${res.statusCode} ${ms.toFixed(1)} ms`);
    });
    next();
  };
}

module.exports = { requestLog };

A line such as POST /api/orders 201 12.4 ms answers, afterwards, the questions every trial raises: what was asked for, what was answered, and how long it took. Errors that were not expected are logged too, with their details, by the error handler, while the browser is told only that something went wrong: details of a failure help an attacker more than a user. The log goes to the program's output, which on the server systemd collects (Chapter 60).

Starting and stopping

src/server.js is the only file that starts anything. It reads the settings, creates the database pool, the store and the clock, builds the application with createApp, and starts listening. It also clears expired sessions once an hour, and when it is asked to stop, by Ctrl+C or by systemd, it stops taking new requests, lets those in progress finish, closes the database connections and exits. The tests never run it: they call createApp directly.

The folders

The folder structure is the layering made visible:

WhereLayerWhat it holds
src/server.jsstart-upreads settings, builds the parts, listens, shuts down cleanly
src/app.jswiringthe middleware pipeline and the routers
src/config.jsconfigurationevery setting, read from the environment
src/middleware/middlewarethe log, security headers, sessions, role guards, errors
src/routes/routesone file per area: auth, menu, orders, canteen, users, health
src/validate.jsvalidationa check for every kind of input
src/services/servicesauth, menu and orders: the use cases
src/rules.jsrulesslots, moves, totals, the canteen's clock, as pure functions
src/store/storeusers, menu and orders: all the SQL
src/db.jsstorethe connection pool, and running work in a transaction
src/errors.jserrorsthe kinds of error the application expects, with their status codes
src/passwords.js, src/attempts.jssecuritypassword hashing, and the limit on failed sign-ins
public/frontendpages, the stylesheet and the scripts (Chapter 27)
sql/databasecreating the database, the schema, the sample data (Chapter 29)
test/testsunit, integration and black-box tests (Chapters 50 to 55)
docs/designthe sources of every diagram in this book
munotes.in171

Backend Architecture

At the time of writing the backend is 27 files and 1,657 lines, of which the largest single file is under 250. Small files with one job each are what the layering produces, and what makes a code review possible (Chapter 49).

Do this for your project

  1. Choose your layers and write the rule: which layer may use which.
  2. Make the business rules pure functions, with no database, clock or request inside them.
  3. Build the application in one function that is given its dependencies, so tests can give it different ones.
  4. Put everything that applies to every request in middleware, in a deliberate order, with the error handler last.
  5. Read every setting from the environment, in one file; commit an example file and never the real one.
  6. Log one line per request, and log unexpected errors in full on the server and never in the browser.
  7. Check the layering in the code, not only in the diagram.

Mistakes that cost marks

SQL in the route handlers. Every change to the database becomes a change to the HTTP code.

Passwords in the code or in the repository. Once committed, a password is in the history for ever.

Error details sent to the browser: stack traces and SQL messages are a map for an attacker.

Business rules that read the clock directly, so they cannot be tested at a time of your choosing.

One file of two thousand lines. Nobody can review it, and everybody edits it at once.

Middleware in an accidental order, such as the error handler first, or the session read after the routes.

Quick revision

  • Layers: routes, validation, services, rules, store; each uses only the layers below it.
  • Pure functions for the rules: the result depends only on the arguments.
  • Dependency injection: parts are given their dependencies, so tests can give different ones.
  • Middleware pipeline, in order: log, security headers, parsing and session, routes, not-found, static files, error handler last.
  • Configuration from the environment, in one file; .env never committed, .env.example always.
  • One log line per request; unexpected errors logged in full on the server, apologised for in the browser.
munotes.in172

Backend Architecture

Questions you must be able to answer

1. What is the layering rule of the worked backend, and why does it matter? Each layer may use only the layers below it: routes use validation and services, services use rules and the store, and the store alone talks to the database. It keeps each change in one place: a new pickup slot changes only the rules, a change of database only the store.

2. What is middleware? Give three examples from the worked backend. Code that every request passes through on its way to a route and every response on its way out, for concerns that apply to everything. The worked backend's request log, its security headers and its session reader are three; the error handler, which runs last, is a fourth.

3. What is dependency injection, and how does it help testing? Giving a part the things it depends on, such as the database pool, the clock and the log, instead of letting it create or fetch them itself. The tests build the same application with a test database, a clock fixed at a chosen moment and a silent log, and so test exactly the code that runs in production.

4. Why are settings read from environment variables? Because they differ between a laptop, the lab and a server, and some are secrets. Reading them from the environment lets the same code run everywhere, and keeps passwords out of the code and out of the repository, where only an example file is committed.

5. Why does the error handler show the user only a general message for an unexpected error? Because the details of an unexpected failure, such as a stack trace or a database message, help an attacker understand the system and do nothing for the user. They are written to the server's log, where the team can read them.

6. What happens to the failed sign-in limit if TRUST_PROXY is not set behind Nginx? Every request appears to come from Nginx's own address on the server. The limit counts failures per address and email together, so with one address for everybody, anyone could lock a student out by typing five wrong passwords for that student's email, and the student would be refused even from their own phone until the window ended.

Contents This chapter on its own page

munotes.in173

Chapter Twenty-Nine

Database Schema Design

Syllabus topic Module 1, "System Architecture Design: ... Database schema design".

In one line

Database schema design turns the ER diagram into tables the database can build: a table for each entity, a column of the right type for each attribute, keys and constraints that let the database itself refuse bad data, indexes for the questions the application asks most, and a structure normalised so that each fact is stored once.

In the wording to use when asked: schema design maps a logical data model to a relational schema: entities to tables, attributes to typed columns, identifiers to primary keys, relationships to foreign keys and associative tables, and business rules to constraints such as NOT NULL, UNIQUE and CHECK; it adds indexes to support the application's queries, and normalises the tables, usually to third normal form, to remove redundancy and the update anomalies it causes.

From the ER diagram to tables

The mapping follows fixed rules:

In the ER diagramIn the schema
an entitya table, usually named in the plural: users
an attributea column, with a type
the keythe primary key
a one-to-many relationshipa foreign key column on the "many" side: orders.user_id
a many-to-many relationshipan associative table with a foreign key to each side: order_items
a one-to-one relationshipa foreign key that is also unique
a weak entitya table whose primary key includes its owner's key
total participation on the "many" sidea foreign key column that is NOT NULL

The worked logical model of Chapter 24 was drawn with the relationships already resolved, so its five entities become exactly five tables.

Choosing the types

Each column's type is a decision about what may be stored in it.

  • Identifiers: INT UNSIGNED AUTO_INCREMENT. The database numbers each new row; unsigned, because an id is never negative.
  • Text: VARCHAR(n), with a limit that matches what the application accepts: 80 characters for a name, 120 for an email address.
  • A fixed set of values: ENUM. A role is exactly one of student, staff and owner; the database refuses anything else.
  • Yes or no: BOOLEAN, which MySQL stores as TINYINT(1), with zero meaning false.
  • Dates and times: DATE for a pickup date, TIME for a pickup slot, DATETIME for when something happened.
  • A fixed-length code: CHAR(64) for a session's identifier, which is a SHA-256 hash written as 64 hexadecimal characters (Chapter 31).

Why money is stored in paise

The worked team recorded this as one of its architecture decisions, ADR-2 (Chapter 36): every amount is a whole number of paise. A Veg Thali at Rs 70.00 is stored as 7000.

The reason is that computers store most fractions only approximately. In JavaScript, 0.1 + 0.2 gives 0.30000000000000004, not 0.3. Add up a day of rupee amounts that way and the report is wrong by a fraction of a paisa, which an owner reconciling cash will notice. MySQL's DECIMAL type is exact, but the application is JavaScript, which has no exact decimal type: the mysql2 driver hands a DECIMAL over as text, or, if asked, as the same inexact kind of number. Whole numbers are exact in the database, in the driver and in JavaScript alike. The pages turn paise into rupees only at the last moment, for display (Chapter 27).

munotes.in174

Database Schema Design

Keys and constraints

A constraint is a rule the database itself enforces, whatever program writes to it. Constraints are the last line of defence: if the application has a bug, the database still refuses the bad row.

  • PRIMARY KEY: unique and never empty. order_items has a composite one, (order_id, menu_item_id), so an item appears in an order only once.
  • FOREIGN KEY: the value must exist in the other table. An order cannot belong to a user who does not exist.
  • ON DELETE CASCADE: when the row referred to is deleted, delete the rows that refer to it. A session goes with its user, and an order's items go with the order: the two compositions of the class diagram (Chapter 24). Where no action is written, MySQL's default applies, which InnoDB treats as RESTRICT: the delete is refused while anything refers to the row. So a menu item that appears in any order can never be deleted, only switched off, and a user with orders can never be deleted either.
  • UNIQUE: no two users share an email address; no two menu items share a name.
  • NOT NULL: the value must be given. Every column in the worked schema is NOT NULL.
  • DEFAULT: the value when none is given. A new order's status is placed.
  • CHECK: any other rule. A price must be between Rs 1 and Rs 1,000, so between 100 and 100000 paise; a quantity between 1 and 5, as FR-7 says. MySQL has enforced CHECK constraints since version 8.0.16; before that it read them and ignored them, which is why the schema's first lines say which version it needs.

Indexes

An index lets the database find rows without reading the whole table, the way a book's index finds a page. Every primary key and unique column gets one automatically. Others are added for the questions the application asks most often:

IndexColumnsThe query it serves
ix_orders_day_slotpickup date, pickup slot, statusthe counter's list for a day and a slot
ix_orders_user_dayuser, pickup datea student's orders today
ix_sessions_expiresexpiry timeclearing expired sessions every hour
munotes.in175

Database Schema Design

In a composite index the order of the columns matters: the index can be used for its first column alone, or its first and second together, and so on, but not for the second column alone. The counter always asks for one day, often for one slot of it, so the day comes first.

Indexes are not free: every insert and update must also update them. Add one for a query the application really makes, not for every column.

Normalisation

Normalisation arranges the tables so that each fact is stored once. When a fact is stored twice, the two copies eventually disagree, and a change must be made in several places. The first three normal forms are the ones a mini project needs:

  • First normal form (1NF): every column holds one value, and there are no repeating groups. An order's items are rows of their own in order_items, not a list in a column of orders.
  • Second normal form (2NF): 1NF, and every non-key column depends on the whole key, not part of it. In order_items, whose key is the order and the item together, the quantity depends on both: how many of this item in this order. The item's name does not belong here, because it depends on the item alone; it is in menu_items.
  • Third normal form (3NF): 2NF, and no non-key column depends on another non-key column. An order does not store the student's name, which depends on the user, not the order; the name is looked up in users when needed.

The worked schema is in third normal form, with two columns that look like repeats and are not accidents:

  • order_items.unit_price_paise repeats the menu's price only at the moment of ordering. It is a different fact, the price this student agreed to, and must not change when the menu's price does.
  • orders.total_paise can be calculated from the order's items. It is stored anyway, a deliberate step away from normalisation, because the total a student saw must be kept exactly, and it is safe because nothing ever changes an order's items once it is placed: the application has no statement that updates them.

The worked schema

This is the file the application actually loads, sql/schema.sql. Every group of the project's tests builds a fresh database from it, on MySQL 8.0 as Ubuntu 24.04 installs it, and the lab will not build unless those tests pass (Chapter 50):

-- Canteen Pre-order: the database schema.
-- Runs on MySQL 8.0.16 or later, which enforces its CHECK rules.
-- Loading it again drops every table first, so all the
-- data in them is lost: use it only to build a fresh copy.

DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS menu_items;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS users;

CREATE TABLE users (
  id            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name          VARCHAR(80)  NOT NULL,
  email         VARCHAR(120) NOT NULL,
  password_hash VARCHAR(200) NOT NULL,
  role          ENUM('student', 'staff', 'owner')
                NOT NULL DEFAULT 'student',
  is_active     BOOLEAN      NOT NULL DEFAULT TRUE,
  created_at    DATETIME     NOT NULL
                DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT uq_users_email UNIQUE (email)
) ENGINE = InnoDB;

-- One row per signed-in browser. The id is a SHA-256 hash
-- of the cookie's value, so a copy of this table cannot be
-- used to sign in as anybody.
CREATE TABLE sessions (
  id         CHAR(64)     NOT NULL PRIMARY KEY,
  user_id    INT UNSIGNED NOT NULL,
  created_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  expires_at DATETIME     NOT NULL,
  CONSTRAINT fk_sessions_user FOREIGN KEY (user_id)
    REFERENCES users (id) ON DELETE CASCADE,
  INDEX ix_sessions_expires (expires_at)
) ENGINE = InnoDB;

-- Prices are whole paise, never rupees with a decimal
-- point: 4500 is Rs 45.00, and integers add up exactly.
CREATE TABLE menu_items (
  id           INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name         VARCHAR(60)  NOT NULL,
  category     ENUM('meals', 'snacks', 'drinks', 'desserts')
               NOT NULL,
  price_paise  INT UNSIGNED NOT NULL,
  is_veg       BOOLEAN      NOT NULL,
  is_available BOOLEAN      NOT NULL DEFAULT TRUE,
  stock_left   INT UNSIGNED NOT NULL DEFAULT 0,
  CONSTRAINT uq_menu_items_name UNIQUE (name),
  CONSTRAINT ck_menu_items_price
    CHECK (price_paise BETWEEN 100 AND 100000)
) ENGINE = InnoDB;

-- The application writes created_at and updated_at itself,
-- from its own clock; the defaults serve rows typed by hand.
CREATE TABLE orders (
  id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id     INT UNSIGNED NOT NULL,
  pickup_date DATE         NOT NULL,
  pickup_slot TIME         NOT NULL,
  status      ENUM('placed', 'preparing', 'ready',
                   'collected', 'cancelled', 'no_show')
              NOT NULL DEFAULT 'placed',
  total_paise INT UNSIGNED NOT NULL,
  created_at  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
              ON UPDATE CURRENT_TIMESTAMP,
  CONSTRAINT fk_orders_user FOREIGN KEY (user_id)
    REFERENCES users (id),
  INDEX ix_orders_day_slot (pickup_date, pickup_slot, status),
  INDEX ix_orders_user_day (user_id, pickup_date)
) ENGINE = InnoDB;

-- Which items an order holds. The price is copied in, so a
-- price changed tomorrow does not rewrite today's orders.
CREATE TABLE order_items (
  order_id         INT UNSIGNED     NOT NULL,
  menu_item_id     INT UNSIGNED     NOT NULL,
  quantity         TINYINT UNSIGNED NOT NULL,
  unit_price_paise INT UNSIGNED     NOT NULL,
  PRIMARY KEY (order_id, menu_item_id),
  CONSTRAINT fk_order_items_order FOREIGN KEY (order_id)
    REFERENCES orders (id) ON DELETE CASCADE,
  CONSTRAINT fk_order_items_item FOREIGN KEY (menu_item_id)
    REFERENCES menu_items (id),
  CONSTRAINT ck_order_items_quantity
    CHECK (quantity BETWEEN 1 AND 5)
) ENGINE = InnoDB;
munotes.in176

Database Schema Design

The databases themselves, and the one account the application uses, are created by a second, shorter file, run once by the database administrator:

-- Run ONCE, as the MySQL administrator (root), before
-- npm run db:setup. It makes the two databases and the one
-- account the application uses, allowed into those two only.
-- Change the password here AND in your .env file.

CREATE DATABASE IF NOT EXISTS canteen
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS canteen_test
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER IF NOT EXISTS 'canteen'@'localhost'
  IDENTIFIED BY 'change-this-password';
GRANT ALL PRIVILEGES ON canteen.* TO 'canteen'@'localhost';
GRANT ALL PRIVILEGES ON canteen_test.* TO 'canteen'@'localhost';
munotes.in177

Database Schema Design

Two details in it are decisions. utf8mb4 is the character set that can store every Unicode character, including the four-byte ones, such as emoji and some rarer scripts, which MySQL's older three-byte utf8mb3 cannot store at all: a student's name must be storable whatever it is written in. And the application's account can reach only its own two databases, the real one and the one the tests use, never the rest of the server.

Matching it to the ER diagram

Chapter 24 promised that the schema is the logical ER diagram column for column. The worked team does not check that by eye: a short script in the book's own tools reads both files and compares every table, every column, its position, its type, its keys and whether it may be empty. It reports:

schema: 5 tables, 30 columns: er.puml and schema.sql agree column for column (names, order, types, keys, NOT NULL)

The diagram abbreviates in one way only, which the script allows: INT for INT UNSIGNED, and ENUM without its list of values. A student project can make the same check by hand, table by table, and should, every time either file changes.

Do this for your project

  1. Turn each entity into a table, each attribute into a typed column, each relationship into a foreign key or an associative table.
  2. Choose each type deliberately; store money as whole numbers of the smallest unit.
  3. Put every rule the data must obey into the schema as a constraint: NOT NULL, UNIQUE, foreign keys, CHECK.
  4. Decide what happens on delete for every foreign key; the default refuses the delete.
  5. Add indexes for the queries your application makes most, with the most selective, always-used column first.
  6. Check each table against 1NF, 2NF and 3NF, and write down any deliberate exception with its reason.
  7. Keep the schema in a SQL file in the repository, and compare it with the ER diagram whenever either changes.

Mistakes that cost marks

Money in FLOAT or DOUBLE, or in rupees with a decimal point in a JavaScript number.

No foreign keys, so orders can point at users who do not exist.

Rules only in the application, so any other program, or a bug, can write bad data.

A comma-separated list in a column: the classic breach of first normal form.

munotes.in178

Database Schema Design

The student's name copied into every order: the classic breach of third normal form.

An index on every column, or none at all.

A schema that only exists inside a GUI tool, with no SQL file anyone can run to build it again.

Quick revision

  • Entity to table, attribute to typed column, key to primary key, 1:N to a foreign key on the many side, M:N to an associative table.
  • Money in whole paise: floats are inexact; JavaScript has no exact decimal.
  • Constraints: PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, DEFAULT, CHECK; the database enforces them whatever writes to it.
  • ON DELETE CASCADE for parts that die with the whole; the default refuses the delete.
  • Indexes for the queries made most; column order matters in a composite index.
  • 1NF one value per column; 2NF depends on the whole key; 3NF no dependency between non-key columns.
  • utf8mb4 for text in any script.

Questions you must be able to answer

1. How is a many-to-many relationship represented in a relational schema? As an associative table with a foreign key to each of the two tables, usually making the pair its primary key, and holding the relationship's own attributes. Orders and menu items become order_items, with order_id, menu_item_id, the quantity and the unit price.

2. Why does the worked schema store money in paise? Because binary floating-point numbers store most fractions only approximately, so rupee amounts added up in them drift; and JavaScript, the application's language, has no exact decimal type. Whole numbers of paise are exact in MySQL, in the driver and in JavaScript.

3. What does ON DELETE CASCADE do, and where does the worked schema use it? It deletes the rows that refer to a row when that row is deleted. The worked schema uses it for a user's sessions and for an order's items, the parts that cannot exist without their whole. Everywhere else the default applies, which refuses the delete.

4. Define 1NF, 2NF and 3NF. First normal form: every column holds a single value and there are no repeating groups. Second: first normal form, and every non-key column depends on the whole primary key. Third: second normal form, and no non-key column depends on another non-key column.

5. The worked orders table stores total_paise, which can be computed. Does that break normalisation, and why was it done? It is a deliberate denormalisation: a derived value stored. It was done because the total a student agreed to must be kept exactly, and it is safe because the application never changes an order's items after the order is placed, so the stored total cannot disagree with them.

munotes.in179

Database Schema Design

6. Why does the worked database use utf8mb4? Because it can store every Unicode character, including the four-byte ones such as emoji, which MySQL's older utf8mb3 cannot store at all, so any student's name can be stored whatever it is written in.

Contents This chapter on its own page

munotes.in180

Chapter Thirty

API Structure

Syllabus topic Module 1, "System Architecture Design: ... API structure".

In one line

An API is the contract between the pages and the server: the list of things the server offers, each named by a method and a path, with what must be sent, what comes back, and what every refusal looks like, written down precisely enough that the frontend and the backend can be built separately and still fit.

In the wording to use when asked: an API structure defines a system's interface as resources identified by URIs and manipulated with HTTP methods according to their defined semantics, with request and response representations (here JSON), status codes that report outcomes, a consistent error format, naming and versioning conventions, and an authentication scheme; the API specification documents every endpoint in these terms.

Why the contract comes first

The worked team built its frontend and its backend at the same time, from 11 September (Chapter 17), and the student's pages were built against a mock of the server before the server existed (Chapter 27). That works only if both sides agree, in writing and in detail, on every request and every answer. The API specification is that agreement. It is also the list the testers work from (Chapter 55), and the first thing an examiner reads to understand what the system can do.

Resources and methods

HTTP is built around resources, things with an address, and methods, what you want done to them. A good API names its resources as nouns and lets the method be the verb:

MethodMeaning, as HTTP defines itSafeIdempotent
GETfetch the resourceyesyes
POSThave the resource process what is sent: usually, create something newnono
PUTreplace the resource with what is sentnoyes
PATCHapply a set of changes to the resourcenono
DELETEremove the resourcenoyes

Two properties from the HTTP specification, RFC 9110, decide which method fits:

  • Safe: the client does not ask for any change on the server. A GET only reads; a page can fetch a menu a thousand times and change nothing.
  • Idempotent: sending the same request several times has the same intended effect as sending it once. Setting today's stock of samosas to 100, twice, still leaves 100, so it is a PUT. Placing an order twice places two orders, so it is a POST.

PATCH comes from a separate specification, RFC 5789, which defines it as applying "a set of changes" to a resource: changing an item's price without resending the whole item.

Status codes

Every answer carries a status code, and a well-designed API uses them for what they mean:

CodeMeaningThe worked API uses it for
200 OKit worked; here is the resultevery successful read or change that returns data
201 Createdit worked, and something new existsa new order, a new account, a new menu item
204 No Contentit worked, and there is nothing to send backsigning out
400 Bad Requestthe request itself is wronginput that fails validation, or a body that is not valid JSON
401 Unauthorizedwho are you?not signed in, or a wrong email and password
403 Forbiddenwe know who you are, and you may notthe wrong role, or a request from another site
404 Not Foundno such thingan unknown address, or an order that is not yours
409 Conflictnot now, because of the current state of thingsa sold-out item, a closed slot, a move from preparing back to placed
413, 415too large; not JSONrequest bodies the server refuses to read
429 Too Many Requestsslow downthe limit on failed sign-ins (NFR-6), with a Retry-After header saying when to try again
500 Internal Server Errorour faultanything the application did not expect
503 Service Unavailablenot available at the momentthe health check, when the database cannot be reached
munotes.in181

API Structure

429 comes from a third specification, RFC 6585, which also allows the answer to say, in a Retry-After header, how long the client should wait.

4xx means the client can fix it; 5xx means the server must. The worked team drew one more line inside the 4xx: 400 is for a request that is wrong whatever the state of the canteen, and 409 for a request that is fine in itself but cannot be done now. "Quantity 7" is a 400, because the limit is 5. "One Chicken Biryani" when none is left is a 409; tomorrow the same request succeeds.

One shape for every error

Every refusal from the worked API has the same body:

{
  "error": {
    "code": "sold_out",
    "message": "Only 2 Chicken Biryani left.",
    "details": { "menuItemId": 3, "stockLeft": 2 }
  }
}
  • code is for programs: a fixed word the pages can test, such as sold_out, that never changes when the wording does.
  • message is for people, in plain words, ready to show.
  • details, when present, says more: which field was wrong, how many are left.

One shape means one piece of code in the pages handles every error (Chapter 27), and the tests can check refusals as precisely as successes. The complete list of codes:

StatusCodes
400invalid_input (with a message per field in details), bad_json
401not_signed_in, wrong_credentials
403forbidden, wrong_origin
404not_found
409email_taken, name_taken, slot_closed, slot_taken, sold_out, item_unavailable, invalid_move
413, 415too_large, json_only
429too_many_attempts (with retryAfterSeconds)
500server_error
munotes.in182

API Structure

One answer has a shape of its own: the health check's, { "status": "down", "database": "unreachable" } with 503, because it is read by a monitor or a person checking the server, not by a page.

Naming

  • Every endpoint lives under /api, so the server can tell a request for data from a request for a page (Chapter 28).
  • Resources are plural nouns, in lower case: /api/orders, /api/menu (a menu is already a collection).
  • One item is the collection plus its id: /api/orders/12.
  • A part of a resource is a path below it: /api/menu/3/stock.
  • An action that is not simply a change of fields gets a name of its own below the resource: /api/orders/12/cancel.
  • Filters are query parameters: /api/orders?slot=12:40.

Versioning, a /api/v1 at the front of every path, lets a public API change without breaking programs other people wrote against it. The worked API has one client, its own pages, released together with the server, so it has no version in its paths; an API offered to other developers should.

Authentication on the API

The worked API signs a user in once, with an email address and a password, and then recognises them by a session cookie: a long random value the server gives the browser, which the browser sends back with every request by itself. Page scripts cannot read it, and it is not sent with other sites' form posts (NFR-4). Every endpoint then decides from the session whether the caller may use it: anyone, any signed-in user, or only certain roles.

A mobile app written natively, not as a web page, would more often send a token in an Authorization header instead of a cookie. The worked Android app shows the same web pages, from the same server, so the cookie works there unchanged (Chapter 59).

Two deviations from HTTP's letter, written down

A careful API says where it bends the rules, and why:

  • 401 without a challenge. RFC 9110 says a 401 response must carry a WWW-Authenticate header naming how to authenticate. That mechanism was designed for HTTP's own schemes, such as Basic; a site that signs in with a form and a cookie has no standard scheme to name, and like most such sites the worked API sends 401 without the header. The pages know what 401 means: a page that finds nobody signed in when it opens goes to the sign-in page, and a 401 later on shows the message "Please sign in first."
  • 201 without a Location. HTTP lets a 201 name the new resource in a Location header. The worked API returns the new order itself, including its id, in the body, which is what the page needs to show the student their number; the order is then at /api/orders/{id}.
munotes.in183

API Structure

The worked API specification

Nineteen endpoints, read from the routes themselves. "Anyone" means no sign-in is needed; "signed in" means any role.

Method and pathWhoWhat it doesSuccessRefusals beyond 400
POST /api/auth/registeranyoneregisters a student, college email only (FR-1)201 { user }409 email_taken
POST /api/auth/loginanyonesigns in and sets the session cookie (FR-2)200 { user }401 wrong_credentials; 429 after five failures
POST /api/auth/logoutanyoneends the session (FR-2)204
GET /api/auth/mesigned inwho is signed in200 { user }401
GET /api/menuanyonetoday's menu (FR-4)200 { items }
POST /api/menuowneradds a menu item (FR-5)201 { item }409 name_taken
PATCH /api/menu/{id}ownerchanges an item's price, or puts it on or off today (FR-5)200 { item }404; 409 name_taken
PUT /api/menu/{id}/stockownersets today's stock (FR-6)200 { item }404
GET /api/slotsanyonethe pickup slots and which are still open (FR-8)200 { slots }
POST /api/ordersstudentplaces an order (FR-7 to FR-11)201 { order }409 slot_closed, slot_taken, sold_out, item_unavailable
GET /api/orders/minestudentmy orders today (FR-12)200 { orders }
GET /api/orderscounter staff, ownertoday's orders, or one slot's with ?slot= (FR-14)200 { date, orders }
GET /api/orders/{id}signed inone order; a student sees only their own (NFR-5)200 { order }404
POST /api/orders/{id}/cancelstudentcancels my order while it is placed (FR-13)200 { order }404; 409 invalid_move
PATCH /api/orders/{id}/statuscounter staff, ownermoves an order to its next status (FR-15)200 { order }404; 409 invalid_move
GET /api/kitchen?slot=counter staff, ownerwhat the kitchen still has to make for a slot (FR-16)200 { date, slot, items }
GET /api/reports/dailyownerthe day's report (FR-17)200 { report }
POST /api/users/staffownercreates a counter staff account (FR-3)201 { user }409 email_taken
GET /api/healthanyoneis the server up, and can it reach the database?200503

Every endpoint that needs a sign-in answers 401 without one, and every role-restricted endpoint answers 403 to the wrong role. Every endpoint that changes something also refuses a body that is not JSON (415) and a request from a page on another site (403 wrong_origin, Chapter 31).

Three design decisions are visible in the table:

  • Someone else's order is "not found", not "forbidden". A 403 would confirm that order 13 exists and belongs to someone. A 404 reveals nothing.
  • Cancelling is an action of its own, for students; moving an order on is a status change, for the counter. A student's only permitted change is cancelling, so the student's side of the API offers exactly that and nothing more general.
  • Setting the stock is a PUT, because setting it to the same number twice is harmless, while every POST in the table creates something new each time it is sent.
munotes.in184

API Structure

Every functional requirement from FR-1 to FR-17 appears in the table, which is the API's own traceability check.

Do this for your project

  1. List your resources as nouns, and give each the methods it needs, by HTTP's meaning: safe, idempotent or neither.
  2. Write one row per endpoint: method, path, who may call it, what it does, the success status and body, every refusal.
  3. Choose one error shape, with a code for programs and a message for people, and list every code.
  4. Draw the line between 400 and 409, and between 401 and 403, and keep to it.
  5. Decide how callers authenticate, and write which endpoints need which role.
  6. Check that every functional requirement appears in the table.
  7. Write down, in the specification, anywhere you depart from HTTP's rules, and why.

Mistakes that cost marks

Verbs in paths: /api/getOrders, /api/createOrder. The method is the verb.

GET that changes something, such as /api/orders/12/cancel fetched with GET: a browser, a crawler or a prefetch can then cancel orders.

200 for everything, with the failure hidden in the body.

A different error shape from every endpoint, so every page handles errors differently.

403 that confirms what exists, where 404 would reveal nothing.

No specification at all, so the frontend is built against what the backend happened to do this week.

Quick revision

  • Resources are nouns under /api; methods are verbs: GET, POST, PUT, PATCH, DELETE.
  • Safe: no change requested (GET). Idempotent: many identical requests, one effect (GET, PUT, DELETE).
  • 2xx success (200, 201, 204); 4xx the client can fix (400, 401, 403, 404, 409, 429); 5xx the server must (500, 503).
  • 400 wrong in itself; 409 not possible now. 401 who are you; 403 you may not.
  • One error shape: a code for programs, a message for people, details.
  • The specification: one row per endpoint, every refusal listed, every requirement covered.

Questions you must be able to answer

1. What is the difference between a safe and an idempotent method? A safe method, such as GET, asks for no change on the server at all. An idempotent method is one where sending the same request several times has the same intended effect as sending it once: PUT and DELETE are idempotent without being safe, and every safe method is also idempotent.

munotes.in185

API Structure

2. Why is setting today's stock a PUT but placing an order a POST? Because setting the stock to a value is idempotent: doing it twice leaves the same stock. Placing an order is not: sending the request twice would place two orders.

3. Distinguish 401 from 403, and 400 from 409. 401 means the server does not know who the caller is, or the credentials were wrong; 403 means it knows who they are and they may not do this. 400 means the request is wrong in itself, whatever the state of the system; 409 means the request is valid but conflicts with the current state, such as an item that has sold out.

4. Why does the worked API answer 404 when a student asks for another student's order? Because a 403 would confirm that the order exists and belongs to someone else. A 404 reveals nothing about it, which is what NFR-5 requires.

5. What goes into a good error response? A consistent shape, with a fixed code a program can test, a message a person can read, and details, such as which field was wrong, where they help.

6. Where does the worked API depart from HTTP's rules, and why is that written down? It sends 401 without the WWW-Authenticate header HTTP requires, because a form-and-cookie sign-in has no standard scheme to name, and it answers 201 with the new order in the body rather than a Location header. Writing deviations down lets anyone using the API know exactly what to expect, and shows the examiner they were decisions, not oversights.

Contents This chapter on its own page

munotes.in186

Chapter Thirty-One

Security Considerations

Syllabus topic Module 1, "System Architecture Design: ... Security considerations".

In one line

Security design means deciding, before building, what an attacker could want from the system and how they could try to get it, and then choosing a defence for each threat, from how passwords are stored and sessions kept to who may do what, what input is trusted, where secrets live and what travels over the network, and writing every decision down so that it can be tested.

In the wording to use when asked: security considerations in system design identify the assets worth protecting, the entry points an attacker could use and the threats to each, and select controls, mapped to a recognised catalogue of risks such as the OWASP Top 10, covering authentication, session management, access control, input validation, output encoding, cryptography for data at rest and in transit, secret management, logging, dependencies and personal data, recorded in a security design that later security testing verifies.

Think like an attacker first

Start from three lists.

What is worth attacking? In the worked system: students' accounts and passwords, their personal data (names, college email addresses), their orders, the owner's control of the menu and the stock, and the system's availability at 12:15, when everyone orders.

Who might attack, and why? A student who wants to see or cancel a friend's order, or order without limits. Someone on the college Wi-Fi reading what others send. A stranger who finds the server and tries passwords. Someone who finds the team's repository. And automated programs that try every site they find.

Where can they get in? Every one of the nineteen API endpoints (Chapter 30), the pages, the network between the phones and the server, the server machine itself, the repository, and the people: the owner's password is worth more than any student's.

Every control in this chapter answers a line in one of those lists. A control that answers nothing on them is decoration; a line with no control is a hole.

The OWASP Top 10

The OWASP Top 10 is the Open Worldwide Application Security Project's list of the ten most important categories of security risk to web applications. The current edition, 2025, names them:

CategoryThe worked design's answer
A01Broken Access Controla role check on every route, deny by default; students see only their own orders; requests from other sites refused (S1, S2, S7)
A02Security Misconfigurationsecurity headers on every response; error details never sent; the application and the database listen only on the machine itself (S11, S14, S17)
A03Software Supply Chain Failurestwo dependencies, at exact versions, installed from a lock file with integrity hashes (S15)
A04Cryptographic Failurespasswords hashed with scrypt; sessions stored as hashes; HTTPS for any public deployment (S3, S6, S12)
A05Injectionevery value in SQL a placeholder; text never inserted as HTML (S8, S9)
A06Insecure Designthis chapter: threats listed and answered before building; the stock rule in one statement (Chapter 22)
A07Authentication Failurespassword rules, a limit on failed sign-ins, no account enumeration (S4, S5, S16)
A08Software or Data Integrity Failuresdatabase constraints and transactions; nothing deserialised but JSON (S10)
A09Security Logging and Alerting Failuresone log line per request, failures included; no alerting, a recorded limitation
A10Mishandling of Exceptional Conditionsone error handler; a rollback on any error inside a transaction (S11)
munotes.in187

Security Considerations

The third column is the worked team's own mapping of its controls to the categories, not OWASP's. The S numbers refer to the security design table at the end of this chapter.

Passwords

Storing them

A password must never be stored as it was typed, and never with a fast hash such as SHA-256, which lets an attacker who steals the table try billions of guesses a second. OWASP's Password Storage Cheat Sheet says what to use instead: a slow, memory-hard algorithm, with a unique random salt for every password, so that two students with the same password have different hashes and no precomputed table helps. It ranks Argon2id first, and scrypt where Argon2id is not available.

The worked team uses scrypt, and the reason is written down. Node.js added Argon2 to its built-in crypto module only in version 24.7.0; on Node.js 22, which the project still supports (NFR-12), it does not exist, as the team confirmed by asking each installed version. scrypt exists on all of them. The cheat sheet lists five scrypt settings of equal strength; the team uses N = 2^14, r = 8, p = 5, which needs 16 MiB of memory per hash.

Each stored hash records its own algorithm and settings beside the salt, in the form scrypt$16384$8$5$salt$key. That makes the choice reversible: when Node.js 22 reaches its end of life, the team can move to Argon2id by re-hashing each password the next time its owner signs in, without breaking any password already stored. And a sign-in compares the computed hash with the stored one in constant time, so the time taken reveals nothing about how close a guess was.

Chapter 8 noted the legal side: under the Information Technology rules of 2011, a password is sensitive personal data, and section 43A of the Information Technology Act requires reasonable security practices for it. Hashing it this way is a large part of what "reasonable" means today.

The rules for choosing one

Two respected standards disagree about length. OWASP's Application Security Verification Standard, ASVS 5.0, requires at least 8 characters and strongly recommends 15. NIST's SP 800-63B-4 says that a password used as the only factor shall be at least 15 characters. Both say the same about everything else: no composition rules, no demand for a capital letter, a digit and a symbol, which make passwords harder to remember and no harder to guess, and a maximum long enough for passphrases.

munotes.in188

Security Considerations

The worked team chose 8 to 128 characters, with no composition rules, and wrote down why: the accounts guard lunch orders, not money; registration is open only to college email addresses; failed sign-ins are limited; and a student typing on a phone at the start of the break will not type 15 characters. That meets ASVS level 1, falls short of NIST's rule, and says so. A system guarding anything of real value should take 15.

Reading ASVS against the design found a gap: level 1 also requires that registration refuse at least the 3,000 most common passwords (6.2.4), and the first design did not. It is now part of the design, as control S5: registration will refuse any password on a list of common passwords, and the page will ask for a password used nowhere else. Chapter 46 builds and tests it.

Sessions

After a correct password, the server creates a session: 32 random bytes, 256 bits, given to the browser as a cookie called sid. The database stores only a SHA-256 hash of it, the decision recorded as ADR-3 (Chapter 36), so that a stolen copy of the sessions table cannot be used to sign in as anybody: the hash cannot be turned back into the cookie. Here a fast hash is right, unlike for passwords, because the token is long and random, with nothing to guess.

The cookie is:

  • HttpOnly: page scripts cannot read it, so even an injected script could not steal it;
  • SameSite=Lax: the browser does not send it with a form posted from another site;
  • Secure whenever the site uses HTTPS: it is then never sent over plain HTTP (NFR-4);
  • limited to 8 hours, and deleted from the database at sign-out, so a stolen cookie stops working.

Access control

Deny by default. Every API route states who may call it, with a guard that refuses anyone else: 401 if nobody is signed in, 403 for the wrong role (Chapter 30). A route with no guard is one of the few deliberately public ones: the menu, the slots, signing in and out, registering, and the health check.

Ownership, not just role. Being a student is not enough to see an order; it must be your order. The order service checks the owner on every read and every cancellation, and answers "not found" for anyone else's, so that it does not even confirm the order exists (NFR-5).

munotes.in189

Security Considerations

The rules decide who may move an order. A student may move an order only from placed to cancelled; only the counter staff and the owner may move it any other way (Chapter 23). That check is in the rules, not in the pages, because a page's buttons can be bypassed by anyone who sends the request directly.

Input: validate everything on the server

Every value that arrives is checked on the server, whatever the page already checked: the page's checks exist to help honest users, and an attacker does not use the page. Types, lengths, allowed values and ids are all checked in one place (Chapter 47); a request body over 10 kilobytes is refused before it is read; and the database's own constraints refuse anything that gets past both (Chapter 29).

SQL injection is prevented by never building SQL out of values: every value is sent to MySQL separately, as a placeholder. Where the text of a statement does vary, it varies only by pieces written in the code: how many placeholders there are, whether a slot filter is added, and, when the owner changes some of a menu item's fields, column names taken from a fixed list, never from the request.

Cross-site scripting is prevented by never adding any text to a page as HTML (Chapter 27), and, as a second defence, by a content security policy that forbids the browser to run any script except the site's own files.

Cross-site request forgery, another site making a signed-in student's browser send a request, is prevented three times over: every request that changes something must be JSON, which an ordinary form on another site cannot send; if the browser names the page the request came from, that page must be on this site; and the SameSite cookie is not sent with another site's form posts.

Secrets

Every secret, the database password first, lives in the .env file on the machine that runs the application, which the repository's .gitignore excludes; the repository holds only .env.example, with placeholder values (Chapter 28). A password once committed stays in the repository's history for ever, even after it is deleted from the file, so the rule is: never commit it, not even once.

The database account the application uses can reach only its own two databases, the real one and the tests', and nothing else on the server (Chapter 29). It has every privilege on those two, because the setup script must be able to rebuild them. A stricter design would give the running application a second account allowed only to read and write rows, never to change tables; for one canteen the team judged one account enough, and recorded the choice.

munotes.in190

Security Considerations

HTTPS, and the trial's weakness

Chapter 25 drew the trial deployment honestly: the phones reach the lab desktop over the college Wi-Fi with plain HTTP, because the machine has no public name or address and so no certificate the phones would trust. That is the largest security weakness of the trial, and the design says so:

  • What is exposed: everything sent between a phone and the server: passwords at sign-in, the session cookie with every request, and the orders.
  • To whom: anyone able to capture traffic on the same network.
  • What limits it: sessions end after 8 hours or at sign-out; each password is hashed on the server, so the database itself never holds it; and the registration page will ask for a password used nowhere else (S5), so that one captured password does not open a student's other accounts.
  • The fix: a public server with a name and a certificate, served over HTTPS, with the cookie's Secure flag turned on, COOKIE_SECURE=true, which also makes the server send a header telling browsers to use only HTTPS for the site (Chapters 58 and 60). The same move lifts the trial's other limit, that ordering works only on the college Wi-Fi.

A weakness written down with its reason, its limits and its fix is an engineering decision. The same weakness unmentioned is a hole the examiner finds.

Logging, and what is not logged

The server writes one line for every request, including every refused sign-in and every refusal of access, and logs unexpected errors in full; the browser is told only that something went wrong (Chapter 28). Passwords, cookies and request bodies are never logged. There is no alerting: nobody is paged when sign-ins fail in bursts. For one canteen the team accepted that and recorded it as a limitation.

Personal data

The system keeps the least personal data it can do its job with: a name, a college email address, a password hash and the orders (constraint C-6). No phone numbers, no payment details, no photographs. Chapter 8 set out the law: the Digital Personal Data Protection Act, 2023, whose main obligations come into force on 13 May 2027, and until then section 43A of the Information Technology Act. Collecting less is the cheapest security control there is: data never collected can never leak.

The worked security design

This is the table Module 2's security validation tests line by line (Chapter 65). Each control names where it lives in the code, so a reviewer can check it.

IDThreatControlWhere
S1a student reads or cancels another's orderthe owner is checked on every read and change; others' orders are "not found"order service
S2a student uses counter or owner functionsa role guard on every route; deny by defaultguards, routes
S3a stolen database reveals passwordsscrypt, N = 2^14, r = 8, p = 5, a random salt per password, constant-time comparisonpasswords module
S4guessing passwordsfive failed sign-ins per address and email in 15 minutes, then 429 with a time to waitattempts module, auth service
S5weak or common passwords8 to 128 characters, no composition rules; common passwords refused and a password used nowhere else asked for (to be built, Chapter 46)validation, registration page
S6a stolen or leaked session256-bit random token; only its SHA-256 stored; HttpOnly, SameSite=Lax, Secure under HTTPS; 8 hours; deleted at sign-outauth service and routes
S7another site acting for a signed-in studentchanges must be JSON and from this site; SameSite cookiesecurity middleware
S8SQL injectionevery value a placeholder; column names from a fixed liststore
S9script injected through a menu item's nametext, never HTML; content security policyui module; security middleware
S10malformed or oversized inputevery input validated on the server; 10 kB body limit; database constraintsvalidation, app, schema
S11error details helping an attackerone error handler; details only in the server's logerror middleware
S12eavesdropping on the Wi-Fithe trial's recorded weakness; fixed by a public server with HTTPS and a Secure cookiedeployment, configuration
S13leaked secretssettings in .env, never committed.gitignore, configuration
S14the database reached from the networkMySQL and the application listen on 127.0.0.1; only Nginx faces the networkdeployment
S15a dependency with a known vulnerabilitytwo dependencies at exact versions, installed from the lock file; checked for advisories before every releasepackage files
S16finding out which emails have accountsone message for any failed sign-in, with a dummy hash checked for unknown emails so the time taken is the sameauth service
S17the site framed by another to trick clicksthe browser told never to show it inside another site's framesecurity middleware
munotes.in191

Security Considerations

S16 has one recorded exception: registering with an address that already has an account answers "email taken", which reveals that the address is registered. The team accepted that, because only college addresses can register and the answer is the one a real student needs.

Do this for your project

  1. List your assets, your likely attackers and your entry points.
  2. Go through the OWASP Top 10 and write your answer to each category.
  3. Hash passwords with Argon2id or scrypt at OWASP's settings, with a salt per password; never with a fast hash.
  4. Choose your password rules from a standard, and write down where you differ from it and why.
  5. Keep sessions server-side, with only a hash of the token stored and the cookie HttpOnly, SameSite and Secure under HTTPS.
  6. Put a guard on every route, check ownership as well as role, and validate every input on the server.
  7. Keep every secret out of the repository from the first commit.
  8. Write your weaknesses down with their limits and fixes, and number every control so that testing can check it.
munotes.in192

Security Considerations

Mistakes that cost marks

Passwords stored in plain text, or hashed with MD5 or SHA-256. A fast hash is not password storage.

Checks only in the pages. Anyone can send a request without the page.

Role checks without ownership checks, so any student can read any order by changing the number.

SQL built by joining strings with values from the request.

A database password in the repository, even for one commit.

"Security: we used HTTPS", with nothing else, or claiming HTTPS the deployment does not have.

A list of controls with no threats, or threats with no controls.

Quick revision

  • Start from assets, attackers and entry points; every control answers one.
  • OWASP Top 10:2025: A01 Broken Access Control, A02 Security Misconfiguration, A03 Software Supply Chain Failures, A04 Cryptographic Failures, A05 Injection, A06 Insecure Design, A07 Authentication Failures, A08 Software or Data Integrity Failures, A09 Security Logging and Alerting Failures, A10 Mishandling of Exceptional Conditions.
  • Passwords: Argon2id or scrypt, a salt per password, never a fast hash; the worked settings are scrypt N = 2^14, r = 8, p = 5.
  • Password rules: ASVS at least 8 (15 recommended); NIST 15 for single-factor; no composition rules; refuse common passwords.
  • Sessions: random token, only its hash stored, cookie HttpOnly, SameSite, Secure, limited life.
  • Deny by default; ownership as well as role; validate on the server; placeholders for SQL; text, not HTML.
  • Secrets never committed. Weaknesses written down with limits and fixes.

Questions you must be able to answer

1. Why must passwords not be stored with SHA-256, and what should be used instead? Because SHA-256 is fast, so an attacker with a stolen table can try billions of guesses a second. A slow, memory-hard password hashing algorithm should be used instead, Argon2id or scrypt, with a unique random salt for each password, as OWASP's Password Storage Cheat Sheet advises.

2. Why does the worked project use scrypt rather than Argon2id? Because Argon2 was added to Node.js's built-in crypto module only in version 24.7.0, and the project also supports Node.js 22, where it does not exist. scrypt, OWASP's second choice, exists on every supported version; and because each hash records its algorithm and settings, the passwords can be moved to Argon2id later, at each student's next sign-in.

munotes.in193

Security Considerations

3. How is the session cookie protected? It is a 256-bit random token, of which the database stores only a SHA-256 hash; it is HttpOnly, so scripts cannot read it; SameSite=Lax, so other sites' forms cannot send it; Secure whenever the site uses HTTPS; it expires after 8 hours and is deleted at sign-out.

4. How does the worked design prevent cross-site request forgery? Every request that changes something must be JSON, which an ordinary form on another site cannot send; if the browser names the page the request came from, it must be this site; and the SameSite cookie is not sent with another site's form posts.

5. What is the trial deployment's largest security weakness, and what is its fix? It serves plain HTTP over the college Wi-Fi, so passwords, session cookies and orders can be read by anyone able to capture traffic on that network. The fix is a public server with a name and a certificate, serving HTTPS, with the cookie's Secure flag on.

6. The worked team allows 8-character passwords although NIST says 15. Is that defensible? It is a recorded decision: it meets OWASP ASVS level 1, which requires 8 and recommends 15, and the team gave its reasons, low-value accounts, college-only registration, a limit on failed sign-ins and typing on phones, while stating that it falls short of NIST's rule for single-factor passwords and that a system guarding anything valuable should take 15.

Contents This chapter on its own page

munotes.in194

Chapter Thirty-Two

The Module 1 Documents: What Is Due and How They Connect

Syllabus topic Module 1, "Documentation Deliverables at the End of Module 1: Project Proposal, SRS Document, Complete UML Set, Architecture Design Document".

In one line

Module 1 ends with four documents MU names, a project proposal, an SRS, a complete UML set and an architecture design document, which together say why the system is worth building, exactly what it must do, what it looks like as a model, and how it will be built; each has one job, each refers to the others by the same numbers and names, and all four follow one set of conventions for titles, versions, numbering and references.

In the wording to use when asked: the Module 1 deliverables form a linked documentation set: the proposal justifies and scopes the project and plans it; the software requirements specification states the requirements; the UML set models the system's structure and behaviour; and the architecture design document records the design and its rationale; consistency is maintained through shared identifiers, traceability from requirements to design, and common conventions of version history, numbering and referencing.

The four, and what each is for

DocumentIts one jobIts main readerWhen it is due in the worked planChapter
Project proposalpersuade that the problem is real, worth solving and can be solved in the time, and say howthe guide, who approves the projectfirst, before anything is designed33
SRSstate exactly what the system must do and how welleveryone: the client, the designers, the builders, the testersonce the requirements are gathered34
Complete UML setmodel the system: its users, its structure, its behaviour, its data, where it runsthe team, the guide, the examinerduring the design35
Architecture design documentrecord how the system will be built and whythe builders now, and whoever maintains it laterlast, at the end of the design36

MU prints the four names and nothing else about them: no template, no page count, no required headings. Your college or your guide may give you a format; if they do, it overrides everything in these chapters. Where they do not, the next four chapters give a structure that covers what each document must do, and print the worked team's documents in full.

How they connect

The four documents are one argument, told in four parts, and they must refer to each other precisely:

  • The proposal states the problem, the scope and the plan. It summarises what the SRS will later say in full, and nothing in the SRS may contradict it without a recorded change.
  • The SRS numbers every requirement: FR-1 to FR-17, NFR-1 to NFR-12, constraints C-1 to C-7, assumptions A-1 to A-6, use cases UC-1 to UC-13. Those numbers are the thread through everything else.
  • The UML set draws the use cases with the SRS's own names and numbers, and every diagram names the requirements it realises (Chapter 35).
  • The architecture document records each design decision with the requirements that drove it: NFR-3 is why passwords are hashed with scrypt; FR-9 and NFR-2 are why the stock is taken in one statement (Chapter 36).
munotes.in195

The Module 1 Documents: What Is Due and How They Connect

The instrument that holds the thread is the requirements traceability matrix: one row per requirement, and columns for where it comes from, which use case covers it, which part of the design realises it and, in Module 2, which test proves it. Chapter 9 began the matrix, Chapter 35 adds the design column, and Chapters 54 and 55 add the tests. A requirement with an empty cell in its row is the first thing a guide looks for.

One set of conventions for all four

The advice in this section is the book's, and every rule in it exists to answer a question a reader will otherwise ask.

The title page

Every document starts with: the project's title; the document's name; its version and date; the team's names and roll numbers; the guide's name; the department, the college and the academic year; and, for your university, the paper: MU's Mini Project I, B.Sc. Computer Science, Semester 5. A document that cannot be traced to its team, its date and its version cannot be marked.

The version history

The first page after the title is a table of every version:

VersionDateByWhat changed
0.114 Aug 2026Aditifirst draft of sections 1 to 3
1.025 Aug 2026Aditi, with the teamwalked through with the owner and the counter; their changes made
1.110 Sep 2026Aditi, RohanFR-1: common passwords refused, from the security design
1.224 Sep 2026AditiFR-14: the counter's list refreshes itself every 10 seconds, from the first increment review

That is the worked SRS's own history. The version history is what lets a guide see, at a glance, that the document is alive: that it changed when the project learned something, and when. Versions numbered 0.x are drafts; 1.0 is the first complete version, the one given to the guide; every change after it gets the next number, 1.1, 1.2, and a row saying what changed and why.

Numbering

  • Sections are numbered, 1, 1.1, 1.1.1, so that anyone can say "section 3.2" in a meeting.
  • Requirements, use cases, constraints and assumptions keep their identifiers for ever. A deleted requirement's number is never reused: FR-18 must never mean two different things in two versions.
  • Figures and tables are numbered by section, Figure 4.2, Table 3.1, and every one has a caption that says what it shows, not only what it is: "Figure 4.2: An order's six statuses and the moves between them", not "Figure 4.2: State diagram".
munotes.in196

The Module 1 Documents: What Is Due and How They Connect

References

Every standard, source and figure that is not the team's own is listed at the end, with its version and the date it was read: "OMG Unified Modeling Language, version 2.5.1, December 2017; read 30 September 2026". A claim with no source is an opinion; a standard with no version is ambiguous.

Names

Every document uses the same names for the same things as the diagrams and the code: an order's statuses are placed, preparing, ready, collected, cancelled and no_show, everywhere (Chapter 19). A short glossary at the end of the SRS fixes the words: slot, stock, counter staff, no-show.

File names, and where the documents live

Name every file with the document and its version, in lower case, with no spaces: proposal-v1.0.pdf, srs-v1.1.pdf, uml-set-v1.0.pdf, architecture-v1.0.pdf, and the drafts srs-v0.3.docx. The version in the file name, the version on the title page and the last row of the version history must always agree.

The worked team kept its documents in one shared folder, with every version kept rather than overwritten, and submitted each as a PDF. The sources of the diagrams did not live there: they are text files in the project's repository, under docs/, where every change is recorded by Git and a diagram can be drawn again from its source at any time (Chapter 19). The PDFs contain the drawings; the repository contains the truth.

The worked team's four documents

DocumentVersion 1.0AcceptedLater versions
Project proposalThu 20 Augat the proposal review, Fri 21 Augnone
SRSTue 25 Aug, after the walk-through with the owner and Ganeshat the design review, Fri 11 Sep, as version 1.11.1, Thu 10 Sep; 1.2, Thu 24 Sep
Complete UML setMon 7 Sep, once the ER diagram was finished with the schemaat the design review1.1, Fri 25 Sep: two sequence and two activity diagrams corrected against the first increment's code
Architecture design documentThu 10 Sepat the design reviewnone

The proposal came first because the guide must approve the project before any design is worth doing; the architecture document came last because it records decisions that depend on everything before it. Chapter 37 is the design review at which all four were accepted, and Chapters 33 to 36 print each of them in full.

Do this for your project

  1. Ask your guide whether your college has a format for any of the four documents; use it if so.
  2. Make one title page and one version history layout, and use them in all four documents.
  3. Give every requirement, use case, constraint and assumption an identifier, and never reuse one.
  4. Number every figure and table by section, with a caption that says what it shows.
  5. List every reference with its version and the date you read it.
  6. Name every file with its document and version, keep every version, and submit PDFs.
  7. Keep the traceability matrix beside the documents, and fill a column each time a document is finished.
munotes.in197

The Module 1 Documents: What Is Due and How They Connect

Mistakes that cost marks

Four documents that disagree: a scope in the proposal that the SRS quietly changes, an actor in the UML set that the SRS never mentions.

No version history, or one version for the whole semester, which says the documents were written once, at the end.

Requirements without identifiers, so nothing else can refer to them.

Figures without captions or numbers: "see the diagram above" in a forty-page document.

Files called final.docx, final2.docx and final-final.docx.

Diagrams pasted as screenshots with no source, so they cannot be changed when the design does.

Quick revision

  • The four: project proposal, SRS, complete UML set, architecture design document; MU prints the names and no format.
  • The proposal justifies and plans; the SRS specifies; the UML set models; the architecture document records the design and its reasons.
  • Identifiers (FR, NFR, C, A, UC) are the thread; the traceability matrix holds it.
  • Every document: title page, version history, numbered sections, numbered and captioned figures and tables, dated references, one set of names.
  • Versions: 0.x drafts, 1.0 complete, 1.1 on changes; the file name, title page and history agree.

Questions you must be able to answer

1. What are the four Module 1 documents, and what is each for? The project proposal, which justifies, scopes and plans the project for approval; the software requirements specification, which states exactly what the system must do and how well; the complete UML set, which models the system's users, structure, behaviour, data and deployment; and the architecture design document, which records how the system will be built and why.

2. How do the four documents stay consistent with each other? Through shared identifiers and names: every requirement, use case, constraint and assumption keeps one identifier, which the other documents use; the diagrams use the SRS's names; and a traceability matrix links each requirement to its source, its use case, its design and, later, its tests.

3. What is a version history, and why does a guide look at it? A table at the front of a document listing every version with its date, its author and what changed. It shows that the document changed as the project learned, and when, and it lets a reader find out which version they hold.

munotes.in198

The Module 1 Documents: What Is Due and How They Connect

4. What does MU say about the format of these documents? Only their names, as the deliverables at the end of Module 1. It prints no template, headings or length, so a college's or a guide's format, where one is given, is the one to follow.

5. Why do the worked team's diagram sources live in the repository and not with the documents? Because the sources are text, and in the repository every change to them is recorded and the drawings can be made again at any time; the documents contain the drawings, but the repository holds the version that can be changed and checked.

Contents This chapter on its own page

munotes.in199

Chapter Thirty-Three

The Project Proposal

Syllabus topic Module 1, "Documentation Deliverables at the End of Module 1: Project Proposal".

In one line

A project proposal asks a guide to approve a project, and earns the approval by showing four things: that the problem is real, that solving it is worth the effort, that this team can solve it in this semester, and that the team knows how; with the scope, the plan and the risks written down so that everyone later knows what was agreed.

In the wording to use when asked: a project proposal is the document that initiates a project by presenting the problem and its evidence, the objectives, the scope and its exclusions, the stakeholders, the proposed solution against its alternatives, a summary of feasibility, the development approach, the plan with its schedule and resources, the principal risks, the deliverables and the team, for approval by the sponsor, which for a student project is the guide, and where there is one, the client.

What a proposal must persuade its reader of

A guide reading a proposal is asking four questions, and each section answers one:

  1. Is the problem real? The evidence: what you observed, measured and were told, and by whom.
  2. Is it worth solving? Who suffers, how much, and what solving it would give them.
  3. Can this team solve it, in this semester? The feasibility, the skills, the plan and its margin.
  4. Do they know how? The approach, the plan, and the risks with what will be done about each.

Everything else in the proposal supports one of the four. A section that answers none of them can go.

It also does a fifth job, less visible: it fixes what was agreed. The scope, and above all what is out of scope, the objectives, the plan and the deliverables, signed by the guide and, where there is one, by the client, are what the team points to when someone asks, in week 8, for online payment (Chapter 4).

Its sections

A structure that answers the four questions, drawn from Chapters 3 to 18, in the order a reader needs:

SectionWhat goes in itFrom
Title page and version historyas Chapter 32 sets out32
1. Summarythe whole proposal in one paragraph
2. The problemthe problem statement and its evidence3, 4
3. Objectivesmeasurable outcomes, numbered4
4. Scopewhat is in; what is out, each with its reason4
5. Stakeholderswho is affected, and how each is involved5
6. Proposed solutionthe idea in a paragraph, and the alternatives rejected8
7. Feasibilitythe verdict on each kind, with the full report attached6 to 8
8. Approachthe development model and why15
9. Planthe work breakdown, the schedule, the resources16 to 18
10. Risksthe main risks and what will be done about each6, 8
11. Deliverableswhat will be handed in, and when1, 32
12. Teamwho does what2, 18
13. Approvalsignatures
munotes.in200

The Project Proposal

Three to six pages is enough for a mini project, with the feasibility report attached. A proposal is a summary and a promise; the detail belongs in the documents that follow.

The worked proposal

What follows is the worked team's proposal, version 1.0, as they submitted it on 20 August 2026. It reuses a great deal from earlier chapters, which is the point: a proposal gathers, in one short document, work the team has already done. The college's name is left out here, as everywhere in this book; your title page names yours.

Canteen Pre-order: ordering lunch before the break

Project Proposal, version 1.0, 20 August 2026

Aditi Kulkarni, Farhan Shaikh, Sneha Nair and Rohan D'Souza, T.Y. B.Sc. Computer Science, Semester 5

Mini Project I (Real-World Application Development), University of Mumbai, academic year 2026-27

Department of Computer Science. Project guide: Prof. S. Iyer

VersionDateByWhat changed
0.117 Aug 2026Aditisections 1 to 6, from the observation, the survey and the interviews
1.020 Aug 2026the teamfeasibility, approach, plan, risks and approval added; submitted to the guide

1. Summary

Students at our college must order and wait for lunch at one canteen counter within a 40-minute break; on an average day of our observation, 31.2 students left the queue without buying. We propose Canteen Pre-order, a web application with an Android app, through which students order before the break for a ten-minute pickup slot and pay at the counter on collection, while the counter sees the orders by slot, the kitchen sees what to cook, and the owner manages the menu, the day's stock and the daily report. It is feasible with technology the whole team has used, costs the canteen Rs 9,500 once, and fits in 65 of the semester's 74 working days.

2. The problem

Students at our college have a 40-minute lunch break, from 12:30 to 13:10, and one canteen counter where they must both order and wait for their food. Over five days of observation we counted an average of 212 students served and 31.2 leaving the queue without buying each day, and timed waits averaging 16 minutes and reaching 27 minutes. In our survey, 131 of 180 students said the queue makes them late for the 13:10 lecture at least once a week, and 49 of 180 skip lunch at least twice a week because of it. The canteen loses sales it could have made, and, by the owner's estimate, throws away about Rs 600 of food a day because the kitchen cannot know in advance what will be ordered.

munotes.in201

The Project Proposal

The evidence: our own observation at the counter, Monday 3 to Friday 7 August 2026, with 100 students timed; a survey of 180 students in the week of 3 August, a convenience sample of the class groups; interviews with the owner, the counter staff, the head cook, six students and the IT lab in-charge, 10 to 13 August; and a week of the owner's sales slips.

3. Objectives

By the end of the semester, the project will deliver a system that:

  • O-1 lets a student order lunch before the break and collect it within five minutes of a chosen pickup time;
  • O-2 is expected to halve the number of students who leave the queue without buying, from 31.2 a day, when the canteen uses it;
  • O-3 tells the kitchen, by 12:20 each day, how many of each dish to prepare for each pickup slot;
  • O-4 shows the owner the day's sales without counting slips.

O-2 will be measured during a two-week trial, by counting walk-aways at the counter exactly as in our observation week.

4. Scope

In scope: student accounts, created by the students with their college email address; today's menu, with prices, a vegetarian mark and what is still available; ordering for one of four ten-minute pickup slots, with a stock limit on every dish; a number for each order, and its status visible to the student; cancelling an order that has not started being prepared; a counter screen of the day's orders by slot, on which staff move each order from placed to collected; a kitchen list of what is still to be made for each slot; the owner's pages for the menu, the day's stock, the daily report and staff accounts; a web application for phones and laptops, and an Android app.

Out of scope:

Not in this projectWhy
Online paymentneeds a payment gateway merchant account and business verification, which the team cannot obtain; students pay at the counter as they do now, which 124 of 180 prefer anyway
Delivery to classroomsthe canteen has no staff to deliver
SMS alertseach message costs money and needs a paid provider account
A penalty for students who order and do not collecta rule the college would have to agree first; noted for a later release
More than one canteenthe college has one
Stock of raw materialsa different problem, the kitchen's, not the queue's

5. Stakeholders

StakeholderInterestHow we involve them
Students, about 212 served a dayeat within the break, not be latesurvey; interviews with six; acceptance test
Ganesh More, counter staffa calmer counter, fewer arguments about the orderinterview; test every counter screen with him
The kitchen: three cooks and a helperknow what to cook, and how much, before the rushinterview with the head cook; test the kitchen list
Lata Pawar, canteen ownermore sales, less waste, a view of each day's takingsinterviews; approve the scope; manage the menu and stock
The principal's officeorder and safety on campus; its email and machinesa letter asking permission; a demonstration before use
The IT lab in-chargea server that is safe, simple and does not need him at lunchtimea meeting about hosting
Teachers of the 13:10 lecturesfewer students arriving lateinformed
munotes.in202

The Project Proposal

6. Proposed solution

A web application, with an Android app, through which students order before the break for a ten-minute pickup slot and pay at the counter on collection; a counter screen for the day's orders; a kitchen list; and the owner's menu, stock and daily report. Alternatives considered and rejected: a second counter (the cost of staff), paper tokens (ordering still queues), a shared online form (no stock control or status), a general delivery platform (built for delivery, not collection).

7. Feasibility

The full feasibility report, version 1.0 of 19 August 2026, is attached. In brief:

  • Technical: feasible with Node.js, Express and MySQL, which all four of us have used, on a lab desktop running Ubuntu 24.04, reached over the college Wi-Fi, whose signal we confirmed throughout the canteen; on condition that the Linux deployment is practised by week 8 and ordering is made safe when many order the last portions at once.
  • Economic: one-time cost Rs 9,500 for a tablet at the counter, no running cost; estimated benefit Rs 465.20 a day; payback about 20 days of college, about 29 if only a quarter of lost sales return.
  • Operational: fits the canteen with two decisions: walk-in sales continue, and the owner allocates each dish's pre-order stock every morning.
  • Legal: we store only a name, a college email address, a password hash and orders, with no payment data; passwords are stored only as salted hashes; the college has permitted the use of its machine and email domain.
  • Schedule: 65 working days of work against 74 available.

8. Approach

We will use an incremental model in two stages. In the design stage (27 July to 10 September), we will complete the requirements, the UML set and the architecture, because MU requires these documents at the end of Module 1 and because our requirements come from stakeholders we can meet only a few times. In the build stage (from 11 September), we will build the system in two increments of two weeks, each ending in a working version that we show to the canteen owner and to our guide, followed by testing, deployment and documentation. We will keep the requirements as a prioritised backlog, meet for fifteen minutes every Tuesday, and hold a review and a retrospective at the end of each increment.

munotes.in203

The Project Proposal

9. Plan

Work. Seven deliverables and twenty-eight work packages, estimated at 240 person-hours: 120 in each module, 30 for each of us in each, as MU's two credits give.

Schedule. Sixteen tasks with their dependencies give a critical path of 65 working days, from choosing the problem to the report, against the 74 working days of the semester from 27 July, leaving 9 days of slack. Key dates: the proposal review, 21 August; the design review, 11 September; the first increment review, 24 September; the second, 9 October; the submission, 26 October.

A Gantt chart of sixteen tasks from 27 July to 26 October 2026, with the design review on 11 September and the submission on 26 October marked as milestones

Figure 33.1 The plan: sixteen tasks, their dependencies and the two milestones

Resources. Our own four laptops; an old desktop in the college lab, offered by the IT lab in-charge, for the server; the owner's phone at the counter for the trial, and a tablet later; free software throughout; no money. Hours are shared out so that none of us works more than 10 hours on this paper in any week.

10. Risks

RiskWhat we will do
Nobody on the team has deployed Node.js on a Linux serverpractise a deployment in week 8, as soon as the first page runs; Rohan owns it
Two students order the last portions at the same moment and both are accepteddesign the stock update to be safe under concurrency, and test it with simultaneous orders
The college Wi-Fi is down at lunchtimethe counter keeps a paper fallback for that day
The lab machine is switched offask for it to stay on in college hours; make the service start by itself at boot
Students order and do not comerecorded, and the food not returned to the stock; a policy is for the college to decide

11. Deliverables

At the end of Module 1, by 11 September: this proposal, the software requirements specification, the complete UML set and the architecture design document. At the end of Module 2, by 26 October: the working application, as a web application and an Android app; the GitHub repository; the final report, with the technical report, the user manual, screenshots and the source code documentation; and the presentation and demonstration.

12. Team

MemberResponsible for
Aditi Kulkarniteam lead: requirements, planning, documents; helps on the frontend
Farhan Shaikhbackend and database
Sneha Nairfrontend and the look of the pages; the user manual
Rohan D'Souzatesting, deployment, the Android app
munotes.in204

The Project Proposal

13. Approval

Approved as the project for Mini Project I, with the scope in section 4.

RoleNameSignatureDate
Project guideProf. S. Iyersigned21 Aug 2026
Canteen owner, the clientLata Pawarsigned21 Aug 2026

At the proposal review

The team met Prof. Iyer for fifteen minutes on Friday 21 August. Three of her questions, and the answers the document already held, show what a proposal is for:

  • "How will you know O-2 has been met?" Section 3 says: by counting walk-aways during the trial, the same way as in the observation week, so the before and after are comparable.
  • "What if the owner asks for online payment in October?" Section 4 says why it is out, and she has signed section 13.
  • "Where is your margin?" Section 9: nine working days, and a first increment that works on its own if everything after it slips (Chapter 15).

A proposal that answers the guide's questions before they are asked is approved in fifteen minutes, as this one was.

Do this for your project

  1. Gather what you already have: the problem statement, the evidence, the scope, the stakeholder register, the feasibility report, the plan and the risks.
  2. Write the objectives so that each can be measured, and say how.
  3. Write out-of-scope items with their reasons; they will be needed.
  4. Keep the proposal short, and attach the feasibility report instead of copying it.
  5. Put the plan's key dates in it, including your college's assessment dates.
  6. Get it signed by your guide and, if you have one, your client.

Mistakes that cost marks

A proposal that describes the app, not the problem. Screens and features before any evidence that anyone needs them.

Objectives nobody can check: "to improve the canteen experience".

No out-of-scope list, so the scope grows every week.

A plan with no dates, or dates with no margin.

Risks that are not risks: "we may not finish" with nothing to be done about it.

A proposal that the later documents contradict without a recorded change.

Quick revision

  • A proposal answers: is the problem real, is it worth solving, can this team solve it in time, do they know how; and it fixes what was agreed.
  • Sections: summary, problem, objectives, scope in and out, stakeholders, solution and alternatives, feasibility, approach, plan, risks, deliverables, team, approval.
  • Short: three to six pages, with the feasibility report attached.
  • Objectives are measurable, with the measure stated.
  • Signed by the guide, and the client where there is one.

Questions you must be able to answer

1. What must a project proposal persuade its reader of? That the problem is real, with evidence; that it is worth solving; that this team can solve it within the semester, as the feasibility, skills and plan show; and that the team knows how, as the approach, plan and risk list show. It also fixes the agreed scope, objectives and plan.

munotes.in205

The Project Proposal

2. Why does a proposal need an out-of-scope list? Because features are requested throughout a project, and a written list of what is excluded, each with its reason and signed off, ends each discussion quickly and keeps the scope from growing beyond the time available.

3. What makes an objective good? Give one from the worked proposal. It states a result that can be measured, and how it will be measured. O-2, to halve the number of students who leave the queue without buying, from 31.2 a day, is measured during the trial by counting walk-aways in the same way as in the observation week.

4. Why is the feasibility report attached rather than copied into the proposal? Because the proposal is a short summary for approval, and the report's detail belongs in its own document; the proposal gives the verdict on each kind of feasibility and points to the report for the working.

5. Who signed the worked proposal, and why both? The guide, who approves it as the project for the paper, and the canteen owner, the client, who agrees to its scope. Both signatures matter later, when a change of scope is asked for.

Contents This chapter on its own page

munotes.in206

Chapter Thirty-Four

The SRS Document

Syllabus topic Module 1, "Documentation Deliverables at the End of Module 1: ... SRS Document".

In one line

A software requirements specification states everything the system must do and how well, precisely enough that the client can confirm it, the designers can design from it, the builders can build to it and the testers can test against it, and it is organised so that each requirement can be found, checked, changed and traced.

In the wording to use when asked: a software requirements specification (SRS) is the document that specifies the functional requirements, non-functional requirements, interfaces, constraints and assumptions of a software product, forming the agreed basis for design, development, verification and acceptance; a good SRS is correct, unambiguous, complete, consistent, ranked for importance and stability, verifiable, modifiable and traceable.

What it is for

The SRS is the project's contract. The client reads it to confirm that it describes what they need. The designers derive the UML set and the architecture from it. The builders build what it says and nothing else. The testers write a test for every requirement in it (Chapter 55). And in the viva, when an examiner asks "does your system do X?", the SRS is what defines the right answer.

Everything in it has already been written in earlier chapters: the stakeholders and users (Chapter 5), the functional requirements (Chapter 10), the non-functional requirements (Chapter 11), the use cases (Chapter 12), the priorities (Chapter 13), the constraints and assumptions (Chapter 14). The SRS gathers them into one document with one structure.

The two standards

IEEE 830-1998, the IEEE Recommended Practice for Software Requirements Specifications, described the content and qualities of a good SRS and gave sample outlines. It is the standard most college SRS templates are still built on. IEEE lists it as superseded, since 2011, by ISO/IEC/IEEE 29148, the international standard for requirements engineering.

The current edition of that standard, ISO/IEC/IEEE 29148:2018, specifies the requirements engineering processes and the information items they produce, including the SRS, with their required content. ISO's own page records that it was last reviewed and confirmed in 2024, and that a revision is in development. The standard is sold rather than published free; this book has not reproduced its outline.

IEEE 830's sample outline, the one most templates follow, is:

SectionIts parts
1. Introduction1.1 Purpose; 1.2 Scope; 1.3 Definitions, acronyms and abbreviations; 1.4 References; 1.5 Overview
2. Overall description2.1 Product perspective; 2.2 Product functions; 2.3 User characteristics; 2.4 Constraints; 2.5 Assumptions and dependencies
3. Specific requirementsthe requirements themselves, which the standard allows to be organised in several ways
Appendixes, and an index

IEEE 830 itself says that an SRS need not follow this outline or use its names, as long as it includes the information the standard discusses. So the rule is simple: if your college gives you a template, use it; if it does not, use this outline, which is what the worked SRS below does.

munotes.in207

The SRS Document

The eight qualities of a good SRS

IEEE 830 lists eight qualities. Each can be checked, and the worked team checked each before giving the SRS to the guide:

QualityWhat it meansHow the worked SRS meets it
Correctevery requirement is one the system really must meetwalked through with the owner, Ganesh and the head cook on 25 August, and corrected
Unambiguousevery requirement has one interpretation"shall"; numbers instead of adjectives; one clock named; terms fixed in the definitions
Completenothing is missing, and nothing is left "to be decided"every use case, user class, constraint and assumption present; no "TBD" anywhere
Consistentno two requirements conflictone set of names, checked against the diagrams (Chapter 19)
Ranked for importance and/or stabilityeach requirement's priority is shownevery functional requirement marked Must or Should (Chapter 13)
Verifiableeach requirement can be checked by a finite, practical testevery non-functional requirement states how it will be verified
Modifiablechanges can be made easily and completelynumbered sections; one requirement per paragraph; a version history
Traceableeach requirement's origin is known, and it can be referred to laterAppendix A: every requirement's source and use case

The worked SRS

Version 1.2, as it stood after the first increment review. The two changes since version 1.0 are marked where they fall.

Canteen Pre-order: Software Requirements Specification

Version 1.2, 24 September 2026

Aditi Kulkarni, Farhan Shaikh, Sneha Nair and Rohan D'Souza, T.Y. B.Sc. Computer Science, Semester 5

Mini Project I (Real-World Application Development), University of Mumbai, academic year 2026-27. Project guide: Prof. S. Iyer

VersionDateByWhat changed
0.114 Aug 2026Aditifirst draft of sections 1 to 3
1.025 Aug 2026Aditi, with the teamwalked through with the owner, Ganesh and the head cook: FR-8's cut-off from 10 to 15 minutes; FR-15, an order not collected keeps its stock
1.110 Sep 2026Aditi, RohanFR-1: common passwords refused, and a password used nowhere else asked for, from the security design; accepted at the design review, 11 Sep
1.224 Sep 2026AditiFR-14: the counter's list refreshes itself at least every 10 seconds, from the first increment review

1. Introduction

1.1 Purpose. This document specifies the requirements of Canteen Pre-order, a system through which the students of our college order lunch before the break and collect it at the canteen counter. It is written for the canteen owner, who confirms that it describes what the canteen needs; for the project guide; and for the team, who design, build and test the system from it.

munotes.in208

The SRS Document

1.2 Scope. Canteen Pre-order lets students order from today's menu for one of four pickup slots, within the day's stock, and pay at the counter on collection; lets the counter staff see the orders by slot and move each through its statuses; gives the kitchen a count of what to make for each slot; and lets the owner manage the menu, the day's stock, the daily report and the staff accounts. It serves the objectives O-1 to O-4 of the project proposal, version 1.0. Online payment, SMS alerts, delivery, penalties for orders not collected, other canteens and the stock of raw materials are outside it, for the reasons the proposal gives.

1.3 Definitions.

TermMeaning in this document
slot, pickup slotone of the four times at which an order is collected: 12:30, 12:40, 12:50 and 13:00
cut-offthe time after which a slot takes no more orders: 15 minutes before the slot
the canteen's clockthe time in India, whatever the server's own setting
stockthe number of portions of an item still offered for pre-order today
active orderan order that is placed, being prepared or ready
not collectedan order whose student did not come before the break ended; also called a no-show
counter staffthe people who serve at the counter; the owner can do everything they can
walk-ina sale at the counter without a pre-order, which continues as before
paisethe unit in which every amount is stored; Rs 70.00 is 7000 paise

1.4 References. The University of Mumbai syllabus of Mini Project I (Real-World Application Development), B.Sc. Computer Science, Semester 5, from the academic year 2026-27. The project proposal, version 1.0, 20 August 2026, and the feasibility report, version 1.0, 19 August 2026. W3C, Web Content Accessibility Guidelines (WCAG) 2.2. ISO/IEC 25010:2023, the product quality model used to classify section 3.4. OWASP, Password Storage Cheat Sheet, and Application Security Verification Standard 5.0. IEEE 830-1998, whose outline this document follows. Each read on or before the date of the version that first cites it.

1.5 Overview. Section 2 describes the product as a whole: where it fits, what it does, who uses it, and the constraints and assumptions it rests on. Section 3 states every requirement. Appendix A traces each functional requirement to its source and its use case.

2. Overall description

2.1 Product perspective. Canteen Pre-order is a new, self-contained system. It runs on a desktop in the college's computer lab and is used through a web browser on phones, tablets and laptops, or through an Android app that shows the same pages. It exchanges data with no other system: there is no payment gateway, no messaging service and no link to the college's own systems beyond accepting only its email domain.

munotes.in209

The SRS Document

2.2 Product functions. In summary, as use cases:

IDUse casePrimary actor
UC-1RegisterStudent
UC-2Sign in / sign outany user
UC-3Browse today's menuStudent (or anyone)
UC-4Place an orderStudent
UC-5View my ordersStudent
UC-6Cancel an orderStudent
UC-7View orders by slotCounter staff
UC-8Update order statusCounter staff
UC-9View kitchen listCounter staff
UC-10Manage the menuOwner
UC-11Set today's stockOwner
UC-12View daily reportOwner
UC-13Create staff accountOwner

2.3 User characteristics.

User classHow manyWhat they knowHow they will use itWhat matters most to them
Studentabout 212 a dayuse phone apps daily; know the menuon a phone, often on mobile data, in a few minutes between lecturesspeed: order in a few taps, see the order number clearly
Counter staffone at a timelittle computer use; comfortable with a phoneon a tablet or laptop at the counter, during the rush, often while handling moneybig, clear buttons; nothing that needs typing during the break
Owneroneuses a phone for business dailymornings and evenings, not during the rushsetting stock quickly; the day's takings at a glance

2.4 Constraints.

IDConstraintSource
C-1The work must fit 60 hours per student over the semesterMU: 2 credits, 30 hours each
C-2No money for software or hosting; everything used must be freethe team; the college
C-3The system runs on the college's machine and network, so ordering needs the college Wi-Fithe IT lab in-charge
C-4The code is kept in a GitHub repositoryMU: "Version control using GitHub (Mandatory)"
C-5No online payment in this releaseno payment gateway account is obtainable
C-6Personal data is limited to a name, a college email address, a password hash and ordersthe law on personal data; the principal's condition
C-7Built with Node.js, Express, MySQL, HTML, CSS, JavaScript and Kotlinthe team's skills

2.5 Assumptions and dependencies.

IDAssumptionChecked byOwner
A-1Students have a phone with a browser and can join the college Wi-Fithe trial: walk-ins who say they could not order are countedAditi
A-2The owner enters each day's pre-order stock before 11:00a morning checklist; the first week of the trialFarhan
A-3Every student has a college email address and can read itthe principal's office confirmed in writingAditi
A-4The counter has a tablet or laptop on the college Wi-Fi during the breakset up before the trialRohan
A-5The lunch break is 12:30 to 13:10 on every college daychecked against each term's timetableSneha
A-6Students pay the right amount at the counterthe order total is shown on the counter's listGanesh, with the team
munotes.in210

The SRS Document

The system depends on the IT lab in-charge for the lab machine and its network, on the principal's office for the use of the college's email domain, and on the owner for each morning's stock.

3. Specific requirements

3.1 External interfaces.

  • Users: web pages for students, for the counter and for the owner, designed for a phone first and usable at 320 CSS pixels wide (NFR-7), and the same pages inside the Android app.
  • Hardware: students' own phones; a tablet or laptop at the counter; the lab desktop as the server.
  • Software: MySQL 8.0 or later for the data, and 8.0.16 or later for the database to enforce its CHECK rules; Node.js 22 or later to run the server; current browsers (NFR-9).
  • Communications: HTTP over the college network during the trial, and HTTPS when the system is served from a public server with a name.

3.2 Functional requirements. Each is marked with its priority: Must or Should.

FR-1. Register. (Must; changed in 1.1) The system shall let a person create a student account by giving a name of 2 to 80 letters, an email address at the college's own domain, and a password of 8 to 128 characters that is not on the system's list of commonly used passwords, and the registration page shall ask for a password used nowhere else. It shall refuse an email address at any other domain, and an email address that already has an account.

FR-2. Sign in and sign out. (Must) The system shall let a registered user sign in with their email address and password, keep them signed in for 8 hours or until they sign out, and let them sign out. When the email address or the password is wrong, it shall give the same message for both.

FR-3. Counter staff accounts. (Should) The system shall let the owner create a counter staff account with a name, an email address and a password.

FR-4. Today's menu. (Must) The system shall show anyone, signed in or not, today's menu: for each item its name, category, price, whether it is vegetarian, and whether it can be ordered now. An item with fewer than 10 portions left shall show how many are left; an item with none left shall show as sold out.

FR-5. Menu items. (Should) The system shall let the owner add a menu item, giving its name, category, price and whether it is vegetarian; change an item's price; and put an item on or take it off today's menu.

munotes.in211

The SRS Document

FR-6. Today's stock. (Must) The system shall let the owner set, for each item, the number of portions offered for pre-order today, from 0 to 1,000.

FR-7. Place an order. (Must) The system shall let a signed-in student order one or more items that can be ordered now, from 1 to 5 of each item and no more than 10 items in all, for one pickup slot.

FR-8. Pickup slots. (Must) The system shall offer the pickup slots 12:30, 12:40, 12:50 and 13:00, on the canteen's own clock, and shall stop accepting orders for each slot 15 minutes before it.

FR-9. Stock. (Must) The system shall not accept an order for more of any item than is left, and shall reduce each item's stock by the quantity of every order it accepts. If any item of an order cannot be supplied, no part of the order shall be accepted.

FR-10. One order per slot. (Should) The system shall allow a student at most one active order, meaning placed, being prepared or ready, for each pickup slot.

FR-11. Order number. (Must) The system shall give each accepted order a number, and show it to the student as soon as the order is accepted.

FR-12. My orders. (Must) The system shall show a signed-in student their own orders for today, with each order's number, slot, items, total and status, and shall refresh the status at least every 15 seconds while the page is open.

FR-13. Cancel an order. (Should) The system shall let a student cancel their own order while its status is placed, and shall return its items to the stock.

FR-14. The counter's list. (Must; changed in 1.2) The system shall show counter staff and the owner today's orders, all together or for one chosen slot, with each order's number, the student's name, the items, the total and the status, and shall refresh the list at least every 10 seconds while the page is open.

FR-15. Moving an order on. (Must) The system shall let counter staff and the owner move an order from placed to being prepared, from being prepared to ready, and from ready to collected or to not collected, and shall refuse every other change of status. An order marked not collected shall not return its items to the stock.

FR-16. The kitchen list. (Should) The system shall show counter staff and the owner, for a chosen slot today, the total quantity of each item in orders that are placed or being prepared.

FR-17. Daily report. (Should) The system shall show the owner, for today or any chosen date, the number of orders in each status, and the quantity and takings of each item in collected orders, with the day's total takings.

munotes.in212

The SRS Document

3.3 Use cases. The use cases of section 2.2 are written in full in the team's use-case analysis. The one the system exists for is reproduced here.

UC-4 Place an order. Primary actor: Student. Goal: to order lunch for a pickup slot, and know the order number. Preconditions: the student is signed in; at least one item can be ordered and at least one slot is open. Trigger: the student opens the menu page.

Main success scenario: 1. The system shows today's menu, and the pickup slots that are still open with their cut-off times. 2. The student chooses one or more items and a quantity of each, and a pickup slot. 3. The student asks the system to place the order. 4. The system checks that the slot is still open, that the student has no active order for that slot, and that there is enough of every item left. 5. The system takes each item from the stock, records the order as placed, and gives it a number. 6. The system shows the student the order number and the slot.

Extensions: 2a. More than 5 of one item or more than 10 items in all: the menu page will not let the quantity go higher; a request that arrives anyway is refused, naming each problem. 3a. The connection is lost: the page says it cannot reach the canteen server and the order is not placed. 4a. The slot has closed: the order is refused, saying so; the student chooses another slot. 4b. The student already has an active order for this slot: refused, saying so. 4c. An item has been taken off today's menu: refused, naming the item. 4d. Fewer portions are left than were asked for: the whole order is refused, saying how many are left or that the item is sold out, and nothing is taken from the stock. At any step: the student's sign-in has expired: the system refuses the order and asks the student to sign in again.

Postconditions: the order is recorded as placed for the chosen slot and today's date, the stock of each item is reduced by its quantity, and the student has the number. Requirements: FR-7 to FR-11; NFR-1, NFR-2.

3.4 Non-functional requirements. Classified by the characteristics of ISO/IEC 25010:2023; each states how it will be verified.

IDCharacteristicRequirementVerified by
NFR-1performance efficiency: time behaviourwith 100 students ordering in the same minute on the lab server, 95 per cent of menu and order requests are answered within 1 second, and none failsload test
NFR-2performance efficiency: capacity; reliabilitywhen many students order the last k portions of an item at the same moment, exactly k orders are acceptedconcurrency test
NFR-3security: confidentialitypasswords are stored only as salted scrypt hashes with settings from the OWASP Password Storage Cheat Sheet, and are never loggedunit tests and code review
NFR-4security: authenticitythe sign-in cookie cannot be read by page scripts, is not sent with another site's form posts, is sent only over HTTPS when the site uses HTTPS, and lasts 8 hoursintegration tests
NFR-5security: confidentiality, integrityeach role can do only what this SRS allows it, and no student can see or change another student's orderintegration and security tests
NFR-6security: resistanceafter 5 failed sign-ins for one email address from one network address within 15 minutes, further attempts are refused for the rest of that windowintegration test
NFR-7interaction capability: operabilityevery page is usable at 320 CSS pixels wide without sideways scrolling (WCAG 2.2 SC 1.4.10), a one-item order takes no more than 4 taps from the menu page, and every counter action is a single tap, with no typingmeasured in the browser
NFR-8interaction capability: inclusivityevery input has a visible label; text contrast is at least 4.5:1 (SC 1.4.3); tap targets are at least 24 by 24 CSS pixels (SC 2.5.8); every action works from a keyboard (SC 2.1.1)measured
NFR-9compatibility; flexibilityworks in current Chrome, Firefox, Safari and Edge, and in the team's Android appsystem testing
NFR-10reliability: recoverabilitythe service restarts itself within 10 seconds of a crash, and starts when the server bootsserver tests
NFR-11maintainability: testability, analysabilitythe whole automated test suite runs with one command, and the README lets a new developer run the system within 30 minutesa timed trial by a new developer
NFR-12flexibility: installabilityruns on Windows, macOS and Linux with Node.js 22 or later and MySQL 8.0 or laterinstallation on the team's machines
munotes.in213

The SRS Document

3.5 A known limitation. The system keeps one stock figure per item and does not reset it overnight. If the owner does not set the day's stock (A-2), yesterday's remaining figures are offered. In this release the risk is guarded by the owner's morning routine and a check by the counter staff at 11:00. The proper fix, storing the date with each stock figure and treating an old figure as zero, is recorded for a later release.

Appendix A. Traceability

RequirementSourceUse case
FR-1the survey; the students' group interview; the principal's office; the security designUC-1
FR-2the owner's interview; the students' group interviewUC-2
FR-3the owner's interviewUC-13
FR-4the owner's and the students' interviews; the owner's menu boardUC-3
FR-5the owner's interview; the owner's menu boardUC-10
FR-6the owner's and the head cook's interviewsUC-11
FR-7the survey; the students' group interviewUC-4
FR-8the owner's and the head cook's interviews; the walk-throughUC-4
FR-9the owner's and the head cook's interviewsUC-4
FR-10the owner's interviewUC-4
FR-11Ganesh's interviewUC-4
FR-12the students' group interviewUC-5
FR-13the owner's and the students' interviewsUC-6
FR-14Ganesh's interview; the first increment reviewUC-7
FR-15Ganesh's interview; the walk-throughUC-8
FR-16the head cook's interviewUC-9
FR-17the owner's interviewUC-12
munotes.in214

The SRS Document

After the SRS

Chapter 35 adds a design column to Appendix A, naming the diagram, class and table that realise each requirement; Chapters 54 and 55 add the tests. When a requirement changes, as FR-1 and FR-14 did, the version history gets a row and the requirement says which version changed it, so that anyone holding version 1.0 can see exactly what is different.

Do this for your project

  1. Use your college's template if there is one; otherwise, IEEE 830's outline.
  2. Gather what you already have: the user classes, the requirements, the use cases, the priorities, the constraints and assumptions.
  3. Write every requirement with "shall", one per paragraph, with an identifier and a priority.
  4. State how each non-functional requirement will be verified.
  5. Define every term a reader could misread.
  6. Check the eight qualities, one by one, before giving it to your guide.
  7. Walk the client through it, change what they correct, and record each change in the version history.
  8. Keep it alive: every change after version 1.0 gets a new version and a row.

Mistakes that cost marks

An SRS that describes screens instead of requirements: "the page has a blue button".

"TBD" anywhere in a submitted version.

Adjectives instead of numbers: "fast", "secure", "user-friendly".

Requirements that cannot be tested, and no word on how any will be.

Two words for one thing, or one word for two.

No version history, so nobody can tell which version the client approved.

An SRS written after the code, describing what was built.

Quick revision

  • The SRS is the contract: for the client, the designers, the builders and the testers.
  • IEEE 830-1998: the outline most templates use; superseded by ISO/IEC/IEEE 29148 (2011); the 2018 edition is current, confirmed in 2024, with a revision in development.
  • IEEE 830's outline: 1 Introduction (purpose, scope, definitions, references, overview), 2 Overall description (perspective, functions, user characteristics, constraints, assumptions and dependencies), 3 Specific requirements, appendixes, index.
  • Eight qualities: correct, unambiguous, complete, consistent, ranked, verifiable, modifiable, traceable.
  • Every requirement: shall, an identifier, a priority, a source; every NFR: how it is verified.
munotes.in215

The SRS Document

Questions you must be able to answer

1. What is an SRS, and who uses it? The document that specifies what a software system must do and how well: its functional and non-functional requirements, interfaces, constraints and assumptions. The client uses it to confirm what they need, the designers and builders work from it, and the testers write tests against it.

2. What is IEEE 830, and what replaced it? IEEE's Recommended Practice for Software Requirements Specifications, of 1998, which described the content and qualities of a good SRS and gave sample outlines. It was superseded in 2011 by ISO/IEC/IEEE 29148, whose current edition is of 2018.

3. List IEEE 830's qualities of a good SRS. Correct, unambiguous, complete, consistent, ranked for importance and/or stability, verifiable, modifiable and traceable.

4. Give the three main sections of IEEE 830's sample outline. Introduction, with the purpose, scope, definitions, references and an overview; overall description, with the product perspective, functions, user characteristics, constraints, and assumptions and dependencies; and the specific requirements.

5. How does the worked SRS make its requirements verifiable? Every requirement uses numbers rather than adjectives, and every non-functional requirement states how it will be verified: a load test for NFR-1, a concurrency test for NFR-2, measurements in the browser for NFR-7 and NFR-8, and so on.

6. The worked SRS changed twice after version 1.0. How does a reader know what changed? Each change has a row in the version history, with its date, its author and what changed, and each changed requirement says in which version it changed, so FR-1 is marked as changed in 1.1 and FR-14 in 1.2.

Contents This chapter on its own page

munotes.in216

Chapter Thirty-Five

The Complete UML Set

Syllabus topic Module 1, "Documentation Deliverables at the End of Module 1: ... Complete UML Set".

In one line

A complete UML set is the system's diagrams gathered into one document: each numbered, captioned and tied to the requirements it realises, together covering the users, the structure, the behaviour, the data and the deployment, and checked to agree with each other and with the SRS, so that the set describes one system and not six.

In the wording to use when asked: a complete UML set is the consolidated collection of a system's models, the use case, class, sequence, activity, ER and deployment diagrams and any others the design requires, presented with numbering, captions and traceability to the requirements, and verified for mutual consistency, so that each requirement is realised in the design and every design element traces to a requirement.

What makes a set complete

"Complete" means three things, and a set can fail any one of them:

  1. Nothing missing. At least the six kinds MU's syllabus lists: use case, class, sequence, activity, ER and deployment. And any other kind the system needs: the worked team added a state machine, because an order's statuses are a state machine (Chapter 23). One diagram of a kind is not always enough: draw a sequence or activity diagram for every use case whose steps matter.
  2. Nothing unexplained. Every diagram numbered and captioned, with a paragraph saying what it shows and what to notice, and the requirements it realises named.
  3. Nothing contradictory. Every diagram agreeing with the others and with the SRS: the seven checks of Chapter 19.

Assembling the set

The UML set is a document like the other three (Chapter 32): a title page, a version history, and then:

  • A list of figures, so a reader can find any diagram.
  • One section per diagram: the figure, its caption, a short text on what it shows, the requirements and use cases it realises, and where its source is.
  • The cross-check: each of the seven checks, and its result.
  • The design column of the traceability matrix: for each requirement, the diagrams and design elements that realise it.

Keep each diagram readable at the size it will be printed; split a diagram rather than shrink it (Chapter 19). And never paste a screenshot of a diagram whose source you do not have.

The worked UML set

Version 1.1, as it stood after the first increment. Its history:

VersionDateByWhat changed
1.07 Sep 2026Farhan, Rohan and Snehathe ten diagrams, finished the day the ER diagram was finished with the schema; accepted at the design review, 11 Sep
1.125 Sep 2026Farhanthe two sequence diagrams and the two activity diagrams corrected against the ordering code of the first increment

Its list of figures, with the chapter of this book where each is printed and explained:

munotes.in217

The Complete UML Set

FigureDiagramCaptionRealisesChapter
1use casewho uses the system, and for what: three actors, thirteen use casesFR-1 to FR-1720
2classthe system's things: User, Session, Order, OrderItem, MenuItem, and three enumerationsall stored data; FR-9, FR-1521
3sequenceplacing an order: the student, the page and the APIUC-4; FR-7, FR-1122
4sequenceplacing an order inside the server: one transaction, three refusalsUC-4; FR-7 to FR-10; NFR-222
5activityordering, from the student's sideUC-4; FR-7 to FR-1123
6activityserving, from the counter's side, with the kitchenUC-8; FR-1523
7state machinean order's six statuses and the five moves between themFR-13, FR-1523
8ER, Chen's notationthe conceptual data: users, orders, menu itemsstored data24
9ER, crow's footthe logical data: five tables, every columnstored data24
10deploymentwhere the system runs during the trialC-3; SRS 3.1, hardware and communications25

Every source is in the repository under docs/uml, and every figure is redrawn from its source by one command (Chapter 19).

The seven checks, run

CheckResult for the worked set
1. Every functional requirement is covered by a use case, and every use case traces back to at least one requirementall 17 requirements and all 13 use cases, both ways (Chapter 20; SRS Appendix A)
2. Every actor is a user class or an outside system named in the SRSStudent, Counter staff and Owner are the SRS's three user classes, and the SRS names no outside system; the kitchen, which never signs in, is a stakeholder, not an actor (Chapter 20)
3. Every important use case has a sequence or an activity diagramUC-4, placing an order, has two sequence diagrams and an activity diagram; UC-8, moving an order on, has the serving activity diagram and the state machine; each of the other eleven is one request and one answer, which its row of the API table describes (Chapter 30)
4. Every message a sequence diagram sends is something its receiver can doevery message of Figures 3 and 4 checked against a route, a function or a statement in the code (Chapter 22)
5. Every class whose data must survive a restart is in the ER diagram, with the same attributesall five classes, every attribute: the model check below (Chapter 24)
6. Every artifact in the deployment diagram is something the architecture says will be built, and every node existsthe application, the database the schema creates and the Android app; the lab desktop, the phones and the counter's device (Chapter 25)
7. One thing, one namethe same names in every diagram; the one exception, an order's slot stored as pickup_slot, written down and translated in one place (Chapters 21 and 24)
munotes.in218

The Complete UML Set

Checks 5 and 7, and the agreement of the model with the code, are made by two short scripts in the book's tools rather than by eye, and they report:

schema: 5 tables, 30 columns: er.puml and schema.sql agree column for column (names, order, types, keys, NOT NULL)
model: 5 classes, 22 attributes all stored; 3 enumerations match the schema; the state machine's 6 states and 5 moves match Status and rules.js

The first compares the ER diagram with the schema. The second checks that every attribute of every class is a column of its table, that each enumeration has exactly the values its column allows, and that the state machine's states are the Status values and its arrows exactly the moves the code allows. Each script tests itself first: it plants mistakes, a changed type, a missing column, an extra status, a redrawn arrow, and stops if it misses one, so its "agree" can be trusted. Your team can make the same checks by hand, with a printout and a pencil, and should, every time a diagram changes.

The drafts did not pass, and each mistake is told in its diagram's own chapter. Before the design review, the checks found a class called OrderLine while its table was order_items, three attributes missing from the class diagram (Chapter 19), and a deployment diagram that claimed HTTPS the trial cannot have (Chapter 25); all were corrected in version 1.0. Other mistakes survived the review. Once the ordering code existed, comparing the diagrams with it found a sequence diagram that saved an order after rolling it back, with no refusal for a second order in the same slot (Chapter 22), and an ordering diagram that checked the stock and then took it, beside a serving diagram with no kitchen (Chapter 23). Those corrections are version 1.1.

Two lessons follow. Checks made by eye miss things, which is why two of them are worth scripting. And a design is checked again once the code exists: the diagrams describe the system, so when the two disagree, one of them is wrong and must be put right. A set that has never failed its checks has probably never been checked.

The design column

Chapter 9 began the traceability matrix with each requirement's source, and the SRS added its use case. The UML set adds where each requirement is realised in the design, with the figure numbers of the list above:

RequirementUse caseFiguresClasses and tablesAPI
FR-1 RegisterUC-11User; usersPOST /api/auth/register
FR-2 Sign in and sign outUC-21Session; sessionsPOST /api/auth/login, /api/auth/logout
FR-3 Counter staff accountsUC-131User, role staff; usersPOST /api/users/staff
FR-4 Today's menuUC-31MenuItem; menu_itemsGET /api/menu
FR-5 Menu itemsUC-101MenuItem; menu_itemsPOST /api/menu; PATCH /api/menu/{id}
FR-6 Today's stockUC-111MenuItem.stockLeft; menu_items.stock_leftPUT /api/menu/{id}/stock
FR-7 Place an orderUC-41, 3, 4, 5Order, OrderItem; orders, order_itemsPOST /api/orders
FR-8 Pickup slotsUC-44, 5Order.slot; orders.pickup_slotGET /api/slots; POST /api/orders
FR-9 StockUC-42, 4, 5MenuItem.takeStock; menu_itemsPOST /api/orders
FR-10 One order per slotUC-44, 5Order; ordersPOST /api/orders
FR-11 Order numberUC-43, 5Order.id; orders.idPOST /api/orders
FR-12 My ordersUC-51Order, OrderItem; orders, order_itemsGET /api/orders/mine
FR-13 Cancel an orderUC-61, 7Order.canMoveTo; ordersPOST /api/orders/{id}/cancel
FR-14 The counter's listUC-71Order, OrderItem; orders, order_itemsGET /api/orders
FR-15 Moving an order onUC-82, 6, 7Order.canMoveTo, Status; orders.statusPATCH /api/orders/{id}/status
FR-16 The kitchen listUC-91Order, OrderItem; orders, order_itemsGET /api/kitchen
FR-17 Daily reportUC-121Order, OrderItem; orders, order_itemsGET /api/reports/daily
munotes.in219

The Complete UML Set

Every requirement has a design element and an API route. And every route in the API table of Chapter 30 names the requirement it serves, except the two that serve the system itself: who is signed in, and whether the server is up. Module 2 adds the last column, the tests (Chapters 54 and 55).

The problems a guide sends back

  • Diagrams that disagree with each other or with the SRS, found in a minute by comparing names.
  • A missing kind: no deployment diagram, or an ER diagram presented as the class diagram.
  • Unreadable diagrams: forty use cases on one page, or a sequence diagram shrunk to fit.
  • Diagrams with no number, caption or explanation.
  • Diagrams that belong to a different system: a textbook's library example with the names changed.
  • No link to the requirements, so nobody can tell what each diagram is for.

Do this for your project

  1. List the diagrams your system needs: MU's six kinds at least, several of a kind where needed, and any other kind that says something better.
  2. Number and caption every diagram, and write what it shows and which requirements it realises.
  3. Run the seven checks, and fix every disagreement before the design review.
  4. Add the design column to your traceability matrix.
  5. Keep every source in your repository, and redraw every figure from its source before submitting.

Mistakes that cost marks

Six diagrams, one of each, and nothing else, when a use case with a transaction needed its own sequence diagram.

A set assembled the night before, with no cross-check, so the class diagram and the ER diagram name different things.

munotes.in220

The Complete UML Set

A traceability matrix with no design column.

Pictures pasted from different tools at different sizes, with no common numbering.

Quick revision

  • Complete = nothing missing, nothing unexplained, nothing contradictory.
  • The set: title page, version history, list of figures, one section per diagram (figure, caption, text, requirements realised, source), the cross-check, the design column.
  • The seven checks of Chapter 19, run and recorded; some can be made by a script.
  • The design column: for each requirement, the figures, classes, tables and API routes that realise it.

Questions you must be able to answer

1. What makes a UML set complete? It has every kind of diagram the system needs, at least MU's six and any others that say something better; every diagram is numbered, captioned, explained and tied to the requirements it realises; and the diagrams agree with each other and with the SRS.

2. How can you check that a class diagram and an ER diagram agree? Attribute by attribute: every attribute of every stored class must be a column of its table, by one stated naming rule such as camelCase to snake_case, with any exception written down, and each enumeration must have exactly the values its column allows. It can be done by hand with a printout, or by a short script, as this chapter's two are, which compare the files themselves and test themselves by planting mistakes they must catch.

3. What is the design column of a traceability matrix? The column that names, for each requirement, the parts of the design that realise it: its diagrams, classes, tables and API routes. It shows that every requirement is designed for, and that no design element exists without a requirement.

4. Why did the worked team draw a state machine when MU does not list one? Because FR-15's rule, which status may follow which, is exactly what a state machine shows; and because the diagram can then be checked against the database and the code: its states are exactly the statuses the database allows, and its arrows exactly the moves the code allows.

5. Name three problems a guide commonly sends back in a UML set. Diagrams that disagree with each other or with the SRS; diagrams with no number, caption or explanation; and unreadable diagrams, such as one crowded use case diagram where two readable ones were needed.

Contents This chapter on its own page

munotes.in221

Chapter Thirty-Six

The Architecture Design Document

Syllabus topic Module 1, "Documentation Deliverables at the End of Module 1: ... Architecture Design Document".

In one line

The architecture design document records how the system will be built and why: its parts and where each runs, the design of the frontend, the backend, the database, the API and the security, and every decision that would be expensive to reverse, with its reasons; the SRS says what the system must do, and this document says how it will do it.

In the wording to use when asked: an architecture design document describes a system's architecture: the requirements and constraints that drive it, the decomposition into components with their responsibilities and interfaces, the data design, the external interfaces, cross-cutting concerns such as security, the deployment, and the rationale for each significant decision, usually as architecture decision records, together with the risks and limitations accepted.

What it records that the SRS does not

The SRS stops where design begins. IEEE 830, the standard behind most SRS templates (Chapter 34), lists four things an SRS should not normally specify: how the software is divided into modules, which module does what, how information and control flow between the modules, and the choice of data structures. Those four are exactly what the architecture document is for. IEEE 830 allows that a security or safety requirement may reach directly into the design, which is why the worked SRS can name scrypt in NFR-3.

The difference, requirement by requirement:

The SRS saysThe architecture document says
FR-9, NFR-2: never more orders than the stock; exactly k orders for the last k portionseach item's stock is taken by one conditional update inside a transaction, items in id order (ADR-5)
FR-8: the slots run on the canteen's clockthe application is the only clock; the database stores the times it is given (ADR-4)
FR-17: the day's takingsevery amount is a whole number of paise (ADR-2)
NFR-4: a sign-in cookie page scripts cannot read, lasting 8 hours32 random bytes in a cookie, with only their SHA-256 hash stored (ADR-3)
NFR-5: each role can do only what it maya guard on every route, deny by default; another student's order is "not found"
NFR-10: the service restarts itself after a crashthe application runs as a systemd service

The SRS is the question; the architecture document is the answer, with its reasons. Every requirement can be met in several ways. The document says which way was chosen and why the others were not, and that "why" is what the viva asks about.

Its sections

MU prints the document's name and nothing more. The international standard for architecture descriptions, ISO/IEC/IEEE 42010, in its second edition of November 2022, sets requirements for what such a description contains, but its own summary says it prescribes no format. The structure is yours to choose, unless your college gives one.

munotes.in222

The Architecture Design Document

A widely used free template is arc42, by Peter Hruschka and Gernot Starke, in use since 2005. It has twelve sections: introduction and goals, constraints, context and scope, solution strategy, building block view, runtime view, deployment view, crosscutting concepts, architecture decisions, quality requirements, risks and technical debt, and a glossary. It is meant to be tailored to the system it describes.

The worked team used a shorter structure, built around the five headings MU's syllabus gives the architecture: frontend architecture, backend architecture, database schema design, API structure and security considerations. Mapped to arc42:

The worked documentarc42
1 Introduction1 Introduction and goals
2 Architectural drivers1 goals, 2 constraints, 10 quality requirements
3 The system as a whole3 context and scope, 4 solution strategy
4 to 7: frontend, backend, database, API5 building block view, 6 runtime view
8 Security8 crosscutting concepts
9 Deployment7 deployment view
10 Decisions9 architecture decisions
11 Risks and accepted limits11 risks and technical debt
the SRS's definitions12 glossary

The worked architecture design document

Version 1.0, as given to the guide for the design review.

Canteen Pre-order: Architecture Design Document

Version 1.0, 10 September 2026

Aditi Kulkarni, Farhan Shaikh, Sneha Nair and Rohan D'Souza, T.Y. B.Sc. Computer Science, Semester 5

Mini Project I (Real-World Application Development), University of Mumbai, academic year 2026-27. Project guide: Prof. S. Iyer

VersionDateByWhat changed
0.18 Sep 2026Farhanfirst draft: sections 1 to 3, and the decisions
1.010 Sep 2026Farhan, with the teamthe frontend (Sneha) and the security design (Rohan) added; checked against SRS version 1.1 (Aditi); for the design review

1. Introduction

1.1 Purpose. This document records how Canteen Pre-order will be built, and why. It is written for us, who will build the system from it; for the project guide; and for whoever maintains the system after us. What the system must do is specified in the SRS; this document does not repeat it, and refers to its requirements by their identifiers.

1.2 Scope. The whole system specified in SRS version 1.1: the pages, the Android app, the server application, the database, and the deployment for the trial on the college lab's desktop. A deployment on a public server is outlined in section 9, to be designed when it is needed.

1.3 References. Canteen Pre-order: Software Requirements Specification, version 1.1, 10 September 2026; Complete UML Set, version 1.0, 7 September 2026; Project Proposal, version 1.0, 20 August 2026. OWASP Top 10:2025; OWASP Password Storage Cheat Sheet; OWASP Application Security Verification Standard 5.0; NIST SP 800-63B-4. RFC 9110, HTTP Semantics; RFC 5789, PATCH Method for HTTP; RFC 6585, Additional HTTP Status Codes. W3C, WCAG 2.2. M. Nygard, "Documenting Architecture Decisions", 15 November 2011. Each read on or before the date of this version.

munotes.in223

The Architecture Design Document

1.4 Overview. Section 2 lists the requirements and constraints that shape the design, and where each is answered. Section 3 describes the system as a whole. Sections 4 to 8 describe its parts under the five headings of the syllabus: frontend, backend, database, API and security. Section 9 is the deployment, section 10 the decisions with their reasons, and section 11 the risks and the limits we accept. Terms have the meanings the SRS gives them in its section 1.3.

2. Architectural drivers

The requirements and constraints that shape the design most, and where each is answered:

DriverWhat it asks of the designAnswered in
C-2: no moneyonly free software, on our own machines and the college's10 (ADR-1)
C-3: the college's machine and networka server on the lab desktop, reached over the college Wi-Fi9
C-5: no online paymentno payment component; each order's total shown at the counter4, 7
C-6: the least personal dataa name, a college email address, a password hash and orders, and nothing else6, 8
C-7 and our skillsNode.js, Express, MySQL, HTML, CSS and JavaScript; Kotlin for the app10 (ADR-1)
FR-8: the canteen's clockevery slot rule decided on one clock, whatever the server's own setting10 (ADR-4)
FR-9, NFR-2: the stockexactly k orders for the last k portions; an order all or nothing10 (ADR-5)
FR-12, FR-17: totals and takingsamounts that add up exactly10 (ADR-2)
NFR-1: 100 students ordering in one minuteabout fourteen times the expected peak of 7 orders a minute, which one application process can serve; indexes for the lists read most3, 6
NFR-3 to NFR-6: securitypasswords, sessions, roles, the limit on failed sign-ins8 (ADR-3)
NFR-7, NFR-8: phones and accessibilitypages designed for 320 CSS pixels first; WCAG 2.2 checks4
NFR-9: browsers and the appplain pages, nothing to compile; the app shows the same pages4 (ADR-1)
NFR-10: restartsthe operating system's service manager restarts the application9
NFR-11: testabilitylayers; rules as pure functions; every part given what it needs; one command for all tests5
NFR-12: installabilityNode.js 22 or later, MySQL 8.0.16 or later, nothing tied to one operating system5, 6

3. The system as a whole

Canteen Pre-order is a client-server system in three tiers. The presentation tier is the pages, in a browser or inside the Android app, which shows the same pages. The application tier is one Node.js program built with Express. The data tier is one MySQL database on the same machine, which only the application reaches.

munotes.in224

The Architecture Design Document

It is one application with one database, not a set of services: one thing to deploy, one place for the data, and function calls where separate services would need network calls that can fail. Inside, the application is divided into layers (section 5).

An architecture diagram: two clients, browser pages and the Android app, send HTTP to the Middleware of the server application; the middleware passes /api requests to Routes and any other path to the static files in public; Routes call Validation and Services; Validation and Services use Rules; Services call the Store, which alone sends SQL to MySQL

Figure 36.1 The architecture: three tiers, and the application's code in layers

  • The pages and the app speak only HTTP, and only to the application.
  • Every request passes through the middleware first. A path under /api goes to the routes; any other path is a file from public/: a page, a stylesheet or a script.
  • The routes check the input with the validation, then call a service. The services apply the canteen's rules and ask the store for data. The store holds every SQL statement, and is the only part that talks to the database.

How one order travels through these parts is drawn in the UML set, Figures 3 and 4.

4. Frontend architecture

4.1 Pages. A multi-page application: each screen is one HTML page, served as a file, with one script that asks the API for data and draws it.

PageWho sees itWhat it is forUse cases
index.htmleveryonesigning in, and registering as a studentUC-1, UC-2
menu.htmlstudentstoday's menu, the order being put together, placing itUC-3, UC-4
orders.htmlstudentstoday's orders and their status; cancellingUC-5, UC-6
counter.htmlcounter staff and the ownertoday's orders by slot, moving each on, the kitchen listUC-7, UC-8, UC-9
owner.htmlthe ownerthe menu, today's stock, the day's report, staff accountsUC-10 to UC-13

4.2 Structure. No framework and no build step (ADR-1). Each page's script is a JavaScript module that imports two shared modules, and no page does their work any other way:

  • api.js, the only code that talks to the server: it sends and receives JSON, and turns every refusal into one kind of error, shown the same way on every page;
  • ui.js, the helpers every page uses: building elements, formatting rupees from paise, showing messages and errors, and finding out who is signed in.

4.3 Data. The server is the source of truth. A page holds only what it shows and what the user is in the middle of doing, such as the order being put together, and asks the server again for anything else. A page that must show changes made by someone else asks again on a timer: the student's orders page every 15 seconds (FR-12).

4.4 Phones and accessibility. The stylesheet is written for a narrow screen first; a wider screen gets the same page rearranged, with the order beside the menu. Every page must be usable at 320 CSS pixels wide without sideways scrolling (NFR-7). NFR-8's four checks apply to every page: a visible label for every field; text contrast of at least 4.5 to 1; tap targets of at least 24 by 24 CSS pixels; everything usable from a keyboard. Colour never carries a meaning alone.

munotes.in225

The Architecture Design Document

4.5 Text, never HTML. Every value is added to a page as text, never as HTML, so that nothing a user typed, such as a menu item's name, can run as a script (section 8, S9).

4.6 Building against the contract. A page opened with ?mock=1 sends its requests to a mock that answers as section 7 specifies, so the pages can be built while the server is being built.

5. Backend architecture

5.1 Layers. Five layers, and one rule: each layer may use only the layers below it.

LayerIts one jobMay useMust never
Routesturn an HTTP request into a call, and the result into a responsevalidation, servicescontain SQL or business rules
Validationcheck that every input is the right shape and within limitsrules, for constants such as the slotsknow about HTTP or the database
Servicescarry out the use cases: place an order, move it on, sign inrules, the store, transactionstouch a request or a response
Rulesthe canteen's rules as plain functions: slots, moves, totals, timenothing at allread the clock, the database or anything outside their arguments
Storeevery SQL statement in the application, and nothing elsethe databaseknow about HTTP or rules

One exception is allowed, and recorded here: the health check's route asks the database directly whether it answers, because testing that connection is its whole job.

5.2 Parts given, not fetched. One function builds the application from its parts, and each part is given what it needs: the configuration, the database pool, the store, the clock and the log. The tests build the same application with a test database, a clock that stands still and a quiet log, so they exercise the code the server runs (NFR-11).

5.3 The middleware pipeline. Every request passes, in this order: the request log; the security headers; for /api paths only, reading JSON with a 10-kilobyte limit, refusing a change that is not JSON or comes from another site, and finding the session; the routes; a JSON 404 for any unknown /api path; the static files; an HTML 404 page; and last, the error handler, where every error ends.

5.4 Configuration. Every setting is read from environment variables, in one file, and nowhere else: where the application listens; the database and its account; the college's email domain; how long a sign-in lasts, and whether its cookie needs HTTPS; the canteen's time zone and the demonstration time (ADR-4); whether to trust a proxy's report of the client's address; and whether to log requests. Real values live in a .env file that is never committed; the repository holds .env.example, with safe values and a comment for each. Without a database password the application refuses to start.

munotes.in226

The Architecture Design Document

5.5 Logging and errors. One line for every request, once it is answered: the method, the address, the status and the time taken. An unexpected error is logged in full, and the browser is told only that something went wrong. Passwords, cookies and request bodies are never logged.

5.6 Folders. The layers made visible: src/routes, src/validate.js, src/services, src/rules.js, and src/store with src/db.js; beside them src/middleware, src/config.js, src/app.js, which wires the parts together, and src/server.js, the only file that starts anything. Then public/ for the pages, sql/ for the database, test/ for the tests and docs/ for the sources of the diagrams.

6. Database schema design

6.1 Engine. MySQL 8.0.16 or later, the first version that enforces CHECK constraints, with InnoDB tables, which give transactions and foreign keys.

6.2 Tables. The five entities of the UML set's logical data model (Figure 9) become five tables:

TableHoldsPrimary keyRules the database enforces
usersevery account, of all three rolesidemail unique; role one of student, staff, owner; is_active to switch an account off
sessionsone row per signed-in browserid, the SHA-256 hash of the cookiebelongs to a user, and is deleted with it; expiry time indexed
menu_itemsthe menu, and today's stockidname unique; category one of four; price 100 to 100000 paise
ordersone row per order; its id is the order numberidbelongs to a user; status one of six, placed at first
order_itemsthe items of each orderorder_id and menu_item_id togetherquantity 1 to 5; the unit price copied in; deleted with its order

6.3 Types and constraints. Ids are unsigned integers the database numbers itself; text columns have the same limits as the validation; fixed sets of values are ENUMs; money is whole paise (ADR-2); every column is NOT NULL. A session is deleted with its user, and an order's items with the order; nothing else is deleted with what it refers to, so a menu item that appears in any order can never be deleted, only taken off the menu.

6.4 Indexes. Beside the keys: ix_orders_day_slot (pickup date, pickup slot, status) for the counter's list; ix_orders_user_day (user, pickup date) for a student's orders; ix_sessions_expires (expiry time) for clearing expired sessions every hour.

6.5 Normal form. Third normal form, with two deliberate repeats: an order item's unit price, which is the price the student agreed to and must not change with the menu; and an order's total, kept exactly as the student saw it, which is safe because an order's items never change once it is placed.

munotes.in227

The Architecture Design Document

6.6 Times. Every time is written by the application from its own clock (ADR-4); the database's time defaults serve only rows typed in by hand.

The schema itself is sql/schema.sql in the repository, and it agrees with the UML set's Figure 9 column for column.

7. API structure

7.1 Conventions. Every endpoint is under /api and speaks JSON. Resources are plural nouns in lower case, /api/orders, /api/menu; one item is the collection and its id, /api/orders/12; a part of a resource is a path below it, /api/menu/3/stock; an action that is not simply a change of fields has a name below its resource, /api/orders/12/cancel; filters are query parameters, /api/orders?slot=12:40. The paths carry no version number: the API has one client, our own pages, released with the server.

7.2 Methods and status codes. GET reads. POST creates something new, or has the server process what is sent. PUT sets a value, and sending it twice does no harm, which is why setting the stock is a PUT. PATCH changes some of an item's fields. A 4xx status means the client can fix the request, a 5xx that the server must. Within the 4xx, 400 is for a request that is wrong whatever the state of the canteen, and 409 for one that is fine in itself but cannot be done now: a quantity of 7 is a 400; an item that has sold out is a 409.

7.3 Errors. Every refusal has one shape: a code for programs, a message for people, and details where they help.

{
  "error": {
    "code": "sold_out",
    "message": "Only 2 Chicken Biryani left.",
    "details": { "menuItemId": 3, "stockLeft": 2 }
  }
}
StatusCodes
400invalid_input (with a message per field in details), bad_json
401not_signed_in, wrong_credentials
403forbidden, wrong_origin
404not_found
409email_taken, name_taken, slot_closed, slot_taken, sold_out, item_unavailable, invalid_move
413, 415too_large, json_only
429too_many_attempts (with retryAfterSeconds)
500server_error

The health check alone answers in a shape of its own, a status for the server and one for the database, with 200 or 503, because a monitor or a person reads it, not a page.

7.4 Signing in. A user signs in once, with an email address and a password, and is then recognised by a session cookie (section 8). Every endpoint states who may call it: anyone, any signed-in user, or only certain roles.

munotes.in228

The Architecture Design Document

7.5 Endpoints. "Anyone" needs no sign-in; "signed in" means any role.

Method and pathWhoWhat it doesSuccessRefusals beyond 400
POST /api/auth/registeranyoneregisters a student, college email only (FR-1)201 { user }409 email_taken
POST /api/auth/loginanyonesigns in and sets the session cookie (FR-2)200 { user }401 wrong_credentials; 429 after five failures
POST /api/auth/logoutanyoneends the session (FR-2)204
GET /api/auth/mesigned inwho is signed in200 { user }401
GET /api/menuanyonetoday's menu (FR-4)200 { items }
POST /api/menuowneradds a menu item (FR-5)201 { item }409 name_taken
PATCH /api/menu/{id}ownerchanges an item's price, or puts it on or off today (FR-5)200 { item }404; 409 name_taken
PUT /api/menu/{id}/stockownersets today's stock (FR-6)200 { item }404
GET /api/slotsanyonethe pickup slots and which are still open (FR-8)200 { slots }
POST /api/ordersstudentplaces an order (FR-7 to FR-11)201 { order }409 slot_closed, slot_taken, sold_out, item_unavailable
GET /api/orders/minestudentmy orders today (FR-12)200 { orders }
GET /api/orderscounter staff, ownertoday's orders, or one slot's with ?slot= (FR-14)200 { date, orders }
GET /api/orders/{id}signed inone order; a student sees only their own (NFR-5)200 { order }404
POST /api/orders/{id}/cancelstudentcancels my order while it is placed (FR-13)200 { order }404; 409 invalid_move
PATCH /api/orders/{id}/statuscounter staff, ownermoves an order to its next status (FR-15)200 { order }404; 409 invalid_move
GET /api/kitchen?slot=counter staff, ownerwhat the kitchen still has to make for a slot (FR-16)200 { date, slot, items }
GET /api/reports/dailyownerthe day's report (FR-17)200 { report }
POST /api/users/staffownercreates a counter staff account (FR-3)201 { user }409 email_taken
GET /api/healthanyoneis the server up, and can it reach the database?200503

Every endpoint that needs a sign-in answers 401 without one, and every role-restricted endpoint answers 403 to the wrong role. Every endpoint that changes something also refuses a body that is not JSON (415) and a request from a page on another site (403 wrong_origin). Another student's order is "not found", not "forbidden": a 403 would confirm that the order exists.

7.6 Two deviations from HTTP, written down.

  • 401 without a challenge. RFC 9110 says a 401 response must carry a WWW-Authenticate header naming how to authenticate. That mechanism was designed for HTTP's own schemes, such as Basic; a site that signs in with a form and a cookie has no standard scheme to name, and like most such sites ours sends 401 without the header. The pages know what 401 means: a page that finds nobody signed in when it opens goes to the sign-in page, and a 401 later on shows the message "Please sign in first."
  • 201 without a Location. HTTP lets a 201 name the new resource in a Location header. We return the new order itself, with its id, in the body, which is what the page needs to show the student their number; the order is then at /api/orders/{id}.
munotes.in229

The Architecture Design Document

8. Security considerations

8.1 What is worth attacking, and by whom. Students' accounts and passwords; their names and college email addresses; their orders; the owner's control of the menu and the stock; and the system's availability at 12:15, when everyone orders. We design against a student who wants to see or cancel someone else's order, or order without limits; someone on the college Wi-Fi reading what others send; a stranger who finds the server and tries passwords; someone who finds our repository; and programs that try every site they find.

8.2 The controls. Each control names where it will live, so that a reviewer can check it and the security tests can test it line by line.

IDThreatControlWhere
S1a student reads or cancels another's orderthe owner is checked on every read and change; others' orders are "not found"order service
S2a student uses counter or owner functionsa role guard on every route; deny by defaultguards, routes
S3a stolen database reveals passwordsscrypt, N = 2^14, r = 8, p = 5, a random salt per password, constant-time comparisonpasswords module
S4guessing passwordsfive failed sign-ins per address and email in 15 minutes, then 429 with a time to waitattempts module, auth service
S5weak or common passwords8 to 128 characters, no composition rules; common passwords refused and a password used nowhere else asked for (to be built)validation, registration page
S6a stolen or leaked session256-bit random token; only its SHA-256 stored; HttpOnly, SameSite=Lax, Secure under HTTPS; 8 hours; deleted at sign-outauth service and routes
S7another site acting for a signed-in studentchanges must be JSON and from this site; SameSite cookiesecurity middleware
S8SQL injectionevery value a placeholder; column names from a fixed liststore
S9script injected through a menu item's nametext, never HTML; content security policyui module; security middleware
S10malformed or oversized inputevery input validated on the server; 10 kB body limit; database constraintsvalidation, app, schema
S11error details helping an attackerone error handler; details only in the server's logerror middleware
S12eavesdropping on the Wi-Fithe trial's recorded weakness; fixed by a public server with HTTPS and a Secure cookiedeployment, configuration
S13leaked secretssettings in .env, never committed.gitignore, configuration
S14the database reached from the networkMySQL and the application listen on 127.0.0.1; only Nginx faces the networkdeployment
S15a dependency with a known vulnerabilitytwo dependencies at exact versions, installed from the lock file; checked for advisories before every releasepackage files
S16finding out which emails have accountsone message for any failed sign-in, with a dummy hash checked for unknown emails so the time taken is the sameauth service
S17the site framed by another to trick clicksthe browser told never to show it inside another site's framesecurity middleware
munotes.in230

The Architecture Design Document

S16 has one accepted exception: registering with an address that already has an account answers "email taken", which reveals that the address is registered (section 11).

8.3 Passwords. Hashed with scrypt at N = 2^14, r = 8, p = 5, one of the settings the OWASP Password Storage Cheat Sheet lists, with a random salt for each password, and compared in constant time. The cheat sheet ranks Argon2id first, but Node.js has it only from version 24.7.0, and NFR-12 includes Node.js 22, so we use scrypt. Each stored hash records its algorithm and settings, so a later move to Argon2id can re-hash each password at its owner's next sign-in, with nobody's password reset. A password is 8 to 128 characters, with no composition rules: ASVS 5.0's level 1 minimum, and below the 15 characters NIST SP 800-63B-4 requires of a password used as the only factor. We accept that gap because the accounts guard lunch orders, only college addresses can register, failed sign-ins are limited, and students type on phones.

8.4 The trial's weakness. During the trial, phones reach the server over the college Wi-Fi with plain HTTP, because the lab desktop has no public name or address, and so no certificate the phones would trust. Passwords at sign-in, the session cookie and the orders cross the Wi-Fi unencrypted, readable by anyone able to capture its traffic. What limits it: sessions end after 8 hours or at sign-out; the database holds only password hashes; and the registration page will ask for a password used nowhere else. The fix: a public server with a name and HTTPS, with the cookie's Secure flag on (section 9).

8.5 Against the OWASP Top 10:2025. A01 Broken Access Control: S1, S2, S7. A02 Security Misconfiguration: S11, S14, S17. A03 Software Supply Chain Failures: S15. A04 Cryptographic Failures: S3, S6, S12. A05 Injection: S8, S9. A06 Insecure Design: this section, and ADR-5. A07 Authentication Failures: S4, S5, S16. A08 Software or Data Integrity Failures: S10. A09 Security Logging and Alerting Failures: every request logged; no alerting, an accepted limit. A10 Mishandling of Exceptional Conditions: S11, and a rollback on any error inside a transaction.

munotes.in231

The Architecture Design Document

9. Deployment

9.1 For the trial. On the lab desktop the IT lab in-charge has offered, under Ubuntu 24.04 (UML set, Figure 10):

  • Nginx listens on port 80, the only port the system opens to the network, and passes every request to the application.
  • The application listens on 127.0.0.1, port 3000, so it can be reached only through Nginx. It is told to trust Nginx's report of each client's address; otherwise every request would seem to come from Nginx, and the limit on failed sign-ins (NFR-6) would let anyone lock a student out with five wrong passwords.
  • MySQL listens on 127.0.0.1, port 3306, and holds the canteen database, which the application reaches with an account of its own.
  • systemd, Ubuntu's service manager, runs the application as a service: it restarts it if it stops, starts it when the machine boots (NFR-10), and keeps its log.

The clients are students' phones, with a browser or the Android app, which loads the same pages from the same server, and the counter's device, a tablet or a laptop with a browser.

9.2 HTTP now, HTTPS later. The trial uses plain HTTP (section 8.4), with the cookie's Secure flag off. On a public server with a name, Nginx will serve HTTPS on port 443 with a certificate, the Secure flag will go on, and students will be able to order over mobile data as well as the college Wi-Fi. The application, the database and the layers stay the same.

9.3 The code. The code lives in a GitHub repository (C-4). A server installs exactly the versions recorded in the lock file.

10. Architecture decisions

Each decision that would be expensive to reverse is recorded with its title, status, context, decision and consequences. Records are numbered, and a number is never reused. A decision that changes gets a new record, and the old one is marked superseded, never deleted.

ADR-1: Node.js, Express and MySQL, with plain pages and an Android wrapper

Status. Accepted, in the third week, at the feasibility study.

Context. Students order from their phones; the counter and the owner work on a tablet or a laptop; the owner edits the menu. The system runs on an Ubuntu 24.04 desktop in the college lab, reachable on the college Wi-Fi. There is no money for software or hosting (constraint C-2). Each member has 60 hours for the whole paper. All four built an Express application last semester; nobody has written PHP for two years; nobody knows Python web frameworks; nobody has deployed Node.js on Linux.

Decision. We will build one Node.js application with Express, using MySQL through the mysql2 driver, and write the pages in plain HTML, CSS and JavaScript without a framework. The server will answer the pages with JSON. Students who want an app will get an Android app that opens the same pages. We will use Node.js 24, a long-term support line, MySQL 8.0 as Ubuntu 24.04 installs it, with the same version on our own laptops, and exact versions of our two libraries, Express 5.2.1 and mysql2 3.24.5.

Consequences. One language, JavaScript, runs on the server and in the browser, and every member can read every file. Everything is free. Node.js 24 is supported until 30 April 2028, beyond the examination. MySQL 8.0 left Oracle's own support on 21 April 2026, when Oracle moved it to Sustaining Support; Ubuntu 24.04 still ships security fixes for its 8.0 package, which is why we accept it on the lab desktop, and the application must keep to what later versions such as 8.4 LTS also support, so that the server can move when the college's does. Plain pages need no build step and teach the fundamentals, but give no structure of their own, so we will write shared helpers for requests and page elements and use them everywhere. MySQL gives us transactions for the stock rule, which we must use correctly (ADR-5). The Android app is a wrapper around the web pages, not a native app: it works only with the server. Nobody has deployed Node.js on Linux: a practice deployment is planned in week 8 (section 11).

munotes.in232

The Architecture Design Document

ADR-2: Every amount in whole paise

Status. Accepted, 2 September, with the schema.

Context. Prices, order totals and the day's takings (FR-4, FR-12, FR-17) must add up exactly, because the owner checks the takings against the cash. JavaScript's numbers are binary fractions, in which 0.1 + 0.2 is 0.30000000000000004. MySQL's DECIMAL type is exact, but the mysql2 driver hands a DECIMAL to JavaScript as text, or, if asked, as the same inexact kind of number.

Decision. We will store and compute every amount as a whole number of paise: an unsigned integer in the database, an integer in JavaScript. Every column that holds money will end in _paise. The pages will turn paise into rupees only to show them, through one helper.

Consequences. Every sum is exact, in the database, in the driver and in JavaScript. A price limit of Rs 1 to Rs 1,000 becomes 100 to 100000. Nobody can mistake the unit of a column. Every amount shown must pass through the helper, or a page will show 7000 where it means Rs 70.00.

munotes.in233

The Architecture Design Document

ADR-3: Sessions kept in the database, as hashes

Status. Accepted, 9 September, with the security design.

Context. NFR-4 asks for a sign-in that lasts 8 hours, ends at sign-out, and cannot be read by page scripts. The server must recognise the signed-in user on every request. Anyone who obtained a copy of a table of live session tokens could sign in as every user in it.

Decision. We will give each sign-in 32 random bytes in a cookie named sid, marked HttpOnly and SameSite=Lax, and Secure whenever the site uses HTTPS, lasting 8 hours. The database will store only a SHA-256 hash of the token, with its user and its expiry. Signing out deletes the row, and expired rows are cleared every hour.

Consequences. A stolen copy of the sessions table cannot be used to sign in, because a hash cannot be turned back into the token. A session can be ended at once by deleting its row, which a token kept only by the browser could not be until it expired. Every API request that carries the cookie costs one database lookup, by primary key. A fast hash is right here, unlike for passwords, because the token is long and random, with nothing to guess.

ADR-4: One clock, the application's

Status. Accepted, 4 September, with the schema and the API.

Context. FR-8's slots and cut-offs run on the canteen's clock, the time in India. The server may be set to another time zone, and MySQL has a clock of its own; if the application decided by one clock that a slot was open and the database stamped the order by another, the two could disagree. We also need to demonstrate ordering at any time of day, including after every real slot has closed.

Decision. The application will be the only clock. The rules will be given the time, never read it. Every time written to the database will be computed by the application, in the canteen's time zone, a setting whose default is Asia/Kolkata, and the database will store what it is given. A second setting, DEMO_TIME, will stop the clock at a chosen moment, for demonstrations only.

Consequences. The slot rules and every stored time agree, whatever the server's zone. The rules can be tested at any time of day without waiting for it, and the tests can use a clock that stands still. The database's own time defaults serve only rows typed in by hand. DEMO_TIME must be empty in real use, so the server will say so at start-up whenever it is set.

ADR-5: The stock taken by one conditional update, inside a transaction

Status. Accepted, 1 September, with the sequence diagrams.

Context. FR-9 and NFR-2: when many students order the last k portions at the same moment, exactly k orders may be accepted, and an order is accepted whole or not at all. Checking the stock and then taking it, in two steps, would let two orders both see the last plate and both take it. FR-10's one active order per student per slot has the same danger: two taps at the same instant could both find no order yet.

Decision. Each order will be placed in one database transaction. The student's own row is locked first, and their active orders for the slot are counted. Each item's stock is then taken by one statement that changes the row only if the item is on today's menu and enough is left; the number of rows changed says whether it was taken. Items are taken in the order of their ids. If any item cannot be taken, the whole transaction is rolled back and the student is told why.

Consequences. Two orders can never both take the last plate: the database applies their updates one after the other, and the second changes nothing. An order is all or nothing. Transactions that share items lock them in the same order, so they cannot deadlock each other. Rows stay locked until the transaction ends, so it must stay short, with nothing inside it that waits for a person. The rule must be proved by a test in which many orders arrive at the same moment.

munotes.in234

The Architecture Design Document

11. Risks and accepted limits

Risk or limitWhat we will do, or why we accept it
Nobody on the team has deployed Node.js on Linuxa practice deployment on the lab desktop in week 8, 14 to 18 September
Plain HTTP on the college Wi-Fi during the trial (S12)the limits in section 8.4; HTTPS on a public server when one is available
Common passwords are not yet refused (S5)built and tested with the authentication work, before the trial
Passwords of 8 characters, not NIST's 15accepted, for the reasons in section 8.3
No alert when sign-ins fail in burstsaccepted for one canteen; every failure is in the log
One database account, with every privilege on its two databasesaccepted, because the setup script must rebuild them; a second account allowed only to read and write rows would be stricter
Registering with a registered address answers "email taken"accepted: only college addresses can register, and it is the answer a real student needs
Yesterday's stock is offered if the owner does not set today's (SRS 3.5)the owner's morning routine and the counter's check at 11:00; a date stored with each stock figure is recorded for a later release
The Android app works only with the serveraccepted: it is a wrapper around the pages (ADR-1)
Nothing checks automatically that the server is upaccepted for the trial: systemd restarts the application, and the health check answers anyone who asks
munotes.in235

The Architecture Design Document

After version 1.0

The guide accepted the document at the design review on 11 September (Chapter 37), and building began the same day.

It needed no second version. The one change to the requirements since then, FR-14's self-refreshing counter list of 24 September (SRS 1.2), uses what section 4.3 already provides: a page that must show others' changes asks again on a timer. No part, layer, table, endpoint or decision changed. A new version would be due if one did: when a decision changes, which gets a new record superseding the old; when a part is added or removed; and when the code shows the document to be wrong.

The code was then held to it. Chapters 27 to 31 show each part as built: the pages' two shared modules, the application's wiring and pipeline, the schema, the nineteen routes of the API table, and each security control in the code. Each matches its section here.

Where each section is explained in this book:

SectionChapters
2 Architectural drivers10, 11 and 14
3 The system as a whole26
4 Frontend27
5 Backend28
6 Database29, from the ER diagram of 24
7 API30
8 Security31
9 Deployment25 now; 57 to 60 when it is done
10 Decisions26 for ADR-1; 29, 31, 43 and 45 for the other four

Do this for your project

  1. Ask whether your college has a format. If not, use MU's five headings, frontend, backend, database, API and security, with an introduction, the drivers, the whole, the deployment, the decisions and the risks around them.
  2. Start from the drivers: the requirements and constraints that shape your design most, and where each is answered.
  3. Under each heading, name the parts, what each does, and the rules between them.
  4. Record every decision that would be expensive to reverse, with its context and all its consequences, the bad ones included.
  5. Print the API table and the security table in full: the tests will be written from them.
  6. List your risks and the limits you accept, each with its reason or its plan.
  7. Once the code exists, check it against the document, and when they disagree, correct whichever is wrong and give the document a new version if it was.

Mistakes that cost marks

The SRS again, under a new title. Requirements restated, and not one word about how they will be met.

Decisions without reasons, or with only their good consequences. "We used MySQL" earns nothing; why, and at what cost, earns the marks.

munotes.in236

The Architecture Design Document

Technology without versions. "Node.js" could mean a version that stopped getting security fixes years ago.

Security in one sentence: "the system is secure". Name each threat and its control.

A diagram of intentions, which the code does not match.

A changed decision rewritten in place, so nobody can see what was decided before, or why it changed.

Quick revision

  • The SRS says what; the architecture document says how, and why.
  • IEEE 830: an SRS should not normally specify the modules, what each does, the flow between them, or the data structures. Those are the architecture document's.
  • ISO/IEC/IEEE 42010:2022 sets requirements for architecture descriptions, but no format. arc42: twelve sections, free, meant to be tailored.
  • The worked outline: introduction, drivers, the whole, frontend, backend, database, API, security, deployment, decisions, risks.
  • An ADR: title, context, decision, status, consequences; numbered, never reused; superseded, not deleted.
  • The worked records: ADR-1 the stack; ADR-2 paise; ADR-3 hashed sessions; ADR-4 one clock; ADR-5 one conditional update in a transaction.

Questions you must be able to answer

1. What does an architecture design document record that an SRS does not? How the system will be built: its division into parts, what each part does, how they communicate, its data design and its deployment, with the reasons for each significant decision and the risks accepted. The SRS says what the system must do and how well, and IEEE 830 says it should not normally specify modules, their functions, the flows between them or data structures.

2. What standard covers architecture descriptions, and what format does it prescribe? ISO/IEC/IEEE 42010, whose current edition is of 2022. It sets requirements for what an architecture description contains, and prescribes no format, so a template such as arc42, or a college's own, supplies the structure.

3. What is an architecture decision record, and what happens to it when the decision changes? A short record of one significant decision, with a title, its status, the context, the decision and its consequences, good and bad. Records are numbered and numbers are never reused; a changed decision gets a new record, and the old one is marked superseded rather than deleted, so the history of the design stays readable.

4. Why does the worked design store money in paise? Because amounts must add up exactly, and JavaScript's numbers are binary fractions that cannot hold most decimal amounts exactly; MySQL's exact DECIMAL reaches JavaScript as text or as an inexact number. Whole paise are exact in the database, in the driver and in JavaScript, and are turned into rupees only for display.

munotes.in237

The Architecture Design Document

5. How does the worked design ensure two students cannot both buy the last plate? Each order is placed in one transaction, and each item's stock is taken by a single statement that changes the row only if enough is left; the number of rows it changed says whether the stock was taken. The database applies the two students' statements one after the other, so the second changes nothing and that student is told the item has sold out. If any item of an order cannot be taken, the whole order is rolled back.

6. The SRS changed after the architecture document's version 1.0. Why did the document need no new version? Because the change, the counter's list refreshing itself, used a mechanism the design already had: pages that must show others' changes ask again on a timer. No part, layer, table, endpoint or decision changed, and a new version is due only when one of those does, or when the code shows the document to be wrong.

Contents This chapter on its own page

munotes.in238

Chapter Thirty-Seven

The Design Review: Presenting Module 1 to Your Guide

Syllabus topic MU's EVALUATION SCHEME, section C, "Evaluation for Mini Project (2 Credit Courses)", Mini Project I: the internal components "Problem Identification & Project Proposal" and "System Design (SRS, UML Diagrams, Architecture)".

In one line

A design review is the meeting at the end of Module 1 at which the team presents its requirements, its model and its architecture to the guide, shows that the design answers the requirements and can be built in the time left, answers the guide's questions, and leaves with the design accepted or with a list of changes; it is also the natural moment for the guide to judge two of MU's internal components.

In the wording to use when asked: a design review is a formal, scheduled examination of a system's design by a reviewer independent of its authors, held before construction begins, to confirm that the design satisfies the requirements, is internally consistent and feasible within the constraints, and to identify defects and risks while they are cheap to correct; it ends in a recorded decision, to proceed, to proceed after changes, or to redesign, with actions assigned.

Why hold one

A mistake found on paper costs an eraser. The same mistake found in the code costs the code built on top of it, and found after the trial, the students' trust as well. The design review is the last point at which the whole design can be looked at, and changed, before anybody builds on it.

It answers one question: should this design be built? And three smaller ones behind it: does it do what the SRS says; do its parts agree with each other; and can this team build it in the weeks left?

The two components on the table

MU gives the guide four components of 5 marks each. Two of them can be judged once Module 1's documents exist:

MU's componentMarksWhat the guide can judge, by our reading of its name
Problem Identification & Project Proposal5a real problem, the evidence that it is real, and a proposal saying what will be built, for whom, why it is worth building, and that it can be built
System Design (SRS, UML Diagrams, Architecture)5the three documents MU names in the brackets: the SRS, the complete UML set and the architecture design document

MU prints the names and the marks, and nothing else: no dates, no criteria. When your guide judges each component is your college's decision. The worked team's guide looked at the proposal at the proposal review on 21 August (Chapter 33) and at the three design documents at the design review on 11 September.

MU's fourth internal component is called Internal Presentation / Review. Whether a review like this one counts towards it is, again, your college's decision; Chapter 74 treats it as the presentation near the end. Ask your guide, and write the answer in your plan.

munotes.in239

The Design Review: Presenting Module 1 to Your Guide

Preparing

Send each document when it is finished, not all four the night before. The worked team's guide received the proposal on 20 August, the SRS on 25 August, the UML set on 7 September, and the architecture document, with the SRS's version 1.1, on 10 September. A guide who has read the documents asks better questions.

Run the checks first. The seven checks of Chapter 19 on the UML set; the eight qualities of Chapter 34 on the SRS; every requirement's row in the traceability matrix filled as far as the design goes (Chapter 35). A disagreement the team finds is a correction; one the guide finds is a mark lost.

Every member knows every document. The guide may ask anyone anything. A member who can explain only their own part tells the guide exactly who did what, and that the others did not read it.

Bring the sources. The repository with the diagrams' sources and the schema, the documents with their version histories, and the plan. "It is in section 7.5" is a better answer than "we will check".

List your weaknesses yourself. Every design has limits. Stated first, with their reasons, a limit is an engineering decision; found by the guide, it is a hole.

The worked review's agenda

One hour, on Friday 11 September, the last day of week 7. Everyone presents a part, and everyone answers questions on all of it:

MinutesWhatWho
5the problem, and what has changed since the proposal reviewAditi
10the requirements: the SRS, the Musts and the Shoulds, and FR-1's change in version 1.1Aditi
15the model: one use case followed through every diagramFarhan, Rohan
10the architecture: the tiers and layers, the five decisions, the API, the security design and its one weaknessFarhan, Rohan
5the pages: the wireframes, and what the owner and Ganesh said about themSneha
10the plan for Module 2: the two increments, the dates, the risksAditi
5the decision, and the actionseveryone

Five plus ten plus fifteen plus ten plus five plus ten plus five is sixty minutes. Questions came throughout; the agenda left room for them by keeping each part short.

Follow one thread through everything

The strongest way to present a design is not document by document but along one thread: a single use case, followed from the requirement to the test that will prove it. It shows in a few minutes that the documents describe one system. The worked team followed UC-4, placing an order:

WhereWhat it says about placing an order
SRSUC-4 realises FR-7 to FR-11, and must meet NFR-1 and NFR-2
Use case diagramthe Student places an order
Sequence diagramsthe page and the API, then the transaction inside the server that takes the stock
Activity diagramordering, from the student's side
Class diagramOrder, OrderItem and MenuItem, with takeStock
ER diagram and schemaorders, order_items and menu_items, with the quantity limited to 1 to 5
APIPOST /api/orders, with 201 and the four 409 refusals
ArchitectureADR-5, the conditional update in a transaction; ADR-4, the slots on one clock
Securitythe student role's guard (S2); changes only as JSON from this site (S7)
Tests to comea test of many orders for the last portions at once (NFR-2), and a load test of 100 students in one minute (NFR-1)
munotes.in240

The Design Review: Presenting Module 1 to Your Guide

The thread did not catch everything. Two mistakes in these very diagrams survived the review: a sequence diagram that saved an order after rolling it back, and an ordering diagram that checked the stock and then took it. Both were found when the diagrams were compared with the code, and corrected in the UML set's version 1.1 (Chapter 35). A review is a strong check, not a proof.

The questions a guide asks, answered for the worked project

A guide's questions come from a small number of worries: is the problem real, is the scope right, will the design work, do the documents agree, can the team finish, and did everyone take part. The worked team was asked these, and answered from its documents.

"How do you know the problem is real?" We watched the queue for a week, 3 to 7 August, in every lunch break: the mean wait from joining the queue to getting food was 16 minutes and the longest 27, which is 67.5 per cent of the 40-minute break, and 156 students in the five days left the queue without buying. Of 180 students who answered our survey, 131 are late for the 13:10 lecture at least once a week because of the queue, and 142 would order from their phone before the break.

"Is it worth building?" For the owner, we estimate a benefit of Rs 465.20 a day: the profit on half the sales now lost, and a third less food thrown away. The one cost is a counter tablet at Rs 9,500, so it pays for itself within 21 college days. The time given back to students, about 36 hours a day between them, we have not turned into money.

"What have you left out, and why?" Online payment, because no payment gateway account is obtainable (C-5), and 124 of 180 students prefer to pay at the counter anyway; SMS alerts; a penalty for orders not collected; delivery to classrooms; other canteens; and the stock of raw materials. Each is in the proposal's out-of-scope list, which the owner signed.

munotes.in241

The Design Review: Presenting Module 1 to Your Guide

"How do you know the requirements are right?" They come from the observation, the survey and interviews with the owner, Ganesh at the counter and the head cook, and each requirement's source is in the SRS's Appendix A. On 25 August we walked the SRS through with those three, and they changed two requirements: the cut-off moved from 10 to 15 minutes before a slot (FR-8), and an order not collected keeps its stock, because the food was cooked (FR-15).

"Which requirement is the hardest, and how does your design meet it?" FR-9 with NFR-2: when many students order the last few portions at the same moment, exactly as many orders as there are portions may succeed. Each order is placed in one transaction, and each item's stock is taken by one statement that changes the row only if enough is left, so the database applies the orders one after the other and the late ones change nothing (ADR-5). A test of many orders arriving at once will prove it.

"Show me that your diagrams agree." An order's six statuses, placed, preparing, ready, collected, cancelled and no_show, are the same in the class diagram's enumeration, in the state machine and in the schema's status column, and the state machine's five moves are the only ones FR-15 and FR-13 allow. We ran Chapter 19's seven checks before today, and they found a class named differently from its table, three missing attributes and a deployment diagram that claimed HTTPS, all corrected in version 1.0.

"Why Node.js and MySQL, and not PHP or Django?" All four of us built an Express application last semester, nobody has written PHP for two years, and nobody knows a Python web framework. Everything is free, it runs on the lab's Ubuntu desktop, and Node.js 24 is supported until April 2028. It is ADR-1, with its bad consequences too: plain pages give no structure, so we write shared helpers, and nobody has deployed Node.js on Linux, so a practice deployment is planned for next week.

"Where are passwords kept?" Nowhere. Each is hashed with scrypt, with a random salt, at settings the OWASP Password Storage Cheat Sheet lists, and only the hash is stored. The cheat sheet prefers Argon2id, but Node.js 22, which we support, does not have it, and each hash records its settings so that we can move later without resetting anyone's password.

"What is the weakest point of your design?" During the trial, phones reach the server over the college Wi-Fi with plain HTTP, because the lab desktop has no public name and so no certificate, and anyone capturing the Wi-Fi's traffic could read a password as it is sent. Sessions end after 8 hours, the database holds only hashes, and the registration page will ask for a password used nowhere else. The fix is a public server with HTTPS, and it is in the architecture document's risks.

munotes.in242

The Design Review: Presenting Module 1 to Your Guide

"What happens if the server stops during the lunch break?" The service manager restarts it within seconds (NFR-10 allows 10), and no accepted order is lost, because each is committed to the database before the student sees its number. If the Wi-Fi or the lab desktop fails altogether, students buy at the counter as they do today: walk-in sales never stopped. Nothing raises an alarm, which is an accepted limit in the architecture document; the health check tells anyone who asks whether the server and its database are up.

"Can you finish in time?" The plan's critical path is 65 working days against the 74 the semester has, so there are 9 days of slack. The first increment, 11 to 24 September, is planned from the Must Haves and will work on its own: if everything after it slipped, the canteen could still take pre-orders. The Could Haves have no hours; they are built only from time saved.

"Who did what?" Every work package has one owner in the work breakdown structure, and each of us has 30 hours in each module. The documents' version histories say who wrote each version, and the repository records who changed each diagram's source, as it will record every change to the code.

"What will I see next?" The first increment on Thursday 24 September: the menu, placing an order with the stock rule, and the counter's list with its status changes, working end to end.

When a guide asks something you cannot answer, say so, write the question down, and answer it at the next meeting. A guess that turns out wrong costs more than an honest "we will find out".

The record of the review

A review ends in a decision, and the decision is written down the same day, with who does what by when. The worked team's record, sent by Aditi to everyone that afternoon:

Design review: Canteen Pre-order

Friday 11 September 2026, 60 minutes. Present: Prof. S. Iyer, guide; Aditi Kulkarni, Farhan Shaikh, Sneha Nair, Rohan D'Souza.

Reviewed: Software Requirements Specification 1.1; Complete UML Set 1.0; Architecture Design Document 1.0; with the Project Proposal 1.0, accepted on 21 August, for reference.

Decisions: the SRS 1.1, the UML set 1.0 and the architecture document 1.0 are accepted as the design to build. Building starts today with Increment 1, to be shown on Thursday 24 September.

ActionWhoBy
Run the practice deployment on the lab desktop, and report what went wrong and what it taught usRohanthe first increment review, Thu 24 Sep
Show the test of many orders for the last portions at once, passingFarhan, with Rohanthe code review, Thu 1 Oct
munotes.in243

The Design Review: Presenting Module 1 to Your Guide

Both actions answer a risk the guide pointed at: the one deployment skill nobody has, and the one requirement a demonstration cannot prove by clicking.

After the review

Accepted is the usual outcome for a team that has run its own checks, and building starts. Accepted after changes means the documents get new versions with the changes, and the guide looks again at what changed, not at everything. Not accepted is rare, and nearly always about scope: a design that cannot be built in the time left is cut down, and the SRS's priorities say where to cut.

Acceptance does not freeze the documents. The worked SRS reached version 1.2 on 24 September, when Ganesh asked at the first increment review for the counter's list to refresh itself, and the UML set reached version 1.1 on 25 September, when two sequence and two activity diagrams were corrected against the code. Each change has its version and its reason. What the review fixes is the starting point, and a record of it.

Module 1 ends here: four documents accepted, and a design worth building. Module 2 builds it, from Chapter 38.

Do this for your project

  1. Ask your guide when each internal component is judged, and put the dates in your plan.
  2. Send each document as soon as it is finished, with its version.
  3. Run the seven checks, the SRS's eight qualities and the traceability matrix before the review.
  4. Plan an agenda that fits the time, with every member presenting a part.
  5. Follow one use case through every document, from the requirement to the test.
  6. State your design's weaknesses before you are asked, each with its reason and its fix.
  7. Write down the decision and the actions, with names and dates, the same day.

Mistakes that cost marks

Reading the documents aloud. The guide can read; show how the parts fit together instead.

One member who does all the talking, and three who cannot answer a question on what they did not present.

Guessing. An invented answer is found out at the code review or the viva.

Hiding the weak points, which the guide then finds, and asks why nobody mentioned them.

No record. A month later nobody remembers what was agreed, or who was to do what.

Treating acceptance as the end of the documents, so that the code and the design drift apart for the rest of the semester.

Quick revision

  • A design review decides whether a design should be built: does it meet the SRS, do its parts agree, can the team build it in time.
  • Two internal components can be judged once Module 1's documents exist: Problem Identification & Project Proposal (5) and System Design (SRS, UML Diagrams, Architecture) (5). MU prints no dates or criteria: ask.
  • Prepare: documents sent as finished; checks run first; everyone knows everything; sources at hand; weaknesses listed.
  • Present one thread: one use case from requirement to test.
  • End with a recorded decision and actions with owners and dates.
  • Outcomes: accepted, accepted after changes, not accepted; documents stay versioned after it.
munotes.in244

The Design Review: Presenting Module 1 to Your Guide

Questions you must be able to answer

1. What is a design review, and what does it decide? A meeting before building starts at which the design is examined by someone other than its authors, here the guide, to confirm that it meets the requirements, that its parts are consistent and that it can be built in the time left. It decides whether the design should be built as it is, built after changes, or redesigned, and it records that decision with actions.

2. Which of MU's internal components can be judged once the Module 1 documents exist? Problem Identification & Project Proposal, and System Design (SRS, UML Diagrams, Architecture), 5 marks each, both awarded by the project guide. MU prints no dates for them, so when they are judged is the college's decision.

3. Why should every member be able to answer questions on every document? Because the guide may ask anyone anything, and a member who can explain only their own part shows that the others did not read or understand the rest; the same questions return at the code review and in the viva.

4. What does "follow one thread" mean in a design review? Presenting one use case through every document, from its requirement to its diagrams, classes, tables, API endpoint, design decision and the test that will prove it, which shows in a few minutes that the documents describe one consistent system.

5. What should a design review's record contain? The date, who was present, which documents were reviewed and in which versions, the decision taken, and each action with the person who will do it and the date by which it is due.

6. The worked team's documents changed after they were accepted. Is that a problem? No. Acceptance fixes the starting point, not the documents. The SRS changed when a user found a need at the first increment review, and the UML set when the diagrams were checked against the code; each change got a new version and a recorded reason, which is exactly what a guide wants to see.

Contents This chapter on its own page

munotes.in245

Module II

Implementation, Testing, Deployment & Evaluation Phase

munotes.in

Chapter Thirty-Eight

The Build Phase at a Glance

Syllabus topic Module 2 (30 hours), "Implementation, Testing, Deployment & Evaluation Phase", as a whole: its five topic rows and the "Final Deliverables at the End of Module 2".

In one line

Module 2 is the build phase: in its 30 hours you turn the four design documents into a working application, prove that it works with tests, put it where its users can reach it, keep every step of it in GitHub, check its speed and its security, write it up, and hand in four deliverables: the working application, the GitHub repository, the final report, and a presentation with a demonstration.

In the wording to use when asked: Module 2 of Mini Project I covers application development (frontend and backend implementation, database integration, authentication and validation, and error handling), integration and system testing (unit, black-box and integration testing, test case preparation and bug tracking), deployment (cloud deployment or local hosting, an APK build, server configuration where applicable, and version control with GitHub, which MU makes mandatory), performance and security testing, and final documentation, and ends in four deliverables: the working application, the GitHub repository, the final report, and the presentation and demonstration.

What MU prints for Module 2

MU heads the module "Implementation, Testing, Deployment & Evaluation Phase". Under the heading come five topic rows and four deliverables:

MU's topic rowWhat it asks you to doChapters
Application Development: Frontend implementation, Backend implementation, Database integration, Authentication & validation, Error handlingbuild the application the design describes39 to 49
Integration & System Testing: Unit testing, Black-box testing, Integration testing, Test case preparation, Bug trackingprove that it works, at every level, and keep track of what does not50 to 56
Deployment: Cloud deployment / Local hosting, APK build, Server configuration (if applicable), Version control using GitHub (Mandatory)put it where its users are, and keep its history57 to 62
Performance & Security Testing: Basic load testing, Input validation checks, Security validationshow that it is fast enough and hard to misuse63 to 65
Final Documentation: Technical Report, User Manual, Screenshots, Source Code Documentationwrite it up for the examiner, the user and the next developer66 to 69

And the deliverables, printed as "Final Deliverables at the End of Module 2":

DeliverableWhat it isChapter
Working Applicationthe system running, doing what the SRS says, in front of the examiner71
GitHub Repositorythe code, its whole history, its issues and its releases72
Final Reportthe permanent written record of the project73
Presentation & Demonstrationthe project shown and explained, live74

Chapter 70 puts the four together, and Chapters 75 and 76 are the external examination and its viva.

Four details of the wording matter:

  • "Cloud deployment / Local hosting": the slash offers a choice. The worked team hosts its trial locally, on the college lab's desktop, because constraint C-3 requires the college's machine; Chapter 58 covers the cloud.
  • "Server configuration (if applicable)": it applies whenever you run your own server. The worked team does, a Linux desktop with Nginx in front, so Chapter 60 configures it.
  • "Version control using GitHub (Mandatory)" is the only item MU marks mandatory, and the repository is also one of the four deliverables. It is used from the first day, not uploaded at the end (Chapters 61 and 62).
  • System testing appears only in the row's heading, "Integration & System Testing", and not in its list. Test the whole system anyway: the SRS says how each requirement will be verified, and only testing the whole system does that (Chapter 54).
munotes.in246

The Build Phase at a Glance

How the design becomes the build

Module 1's documents are not left behind in Module 2; every one of them becomes something that runs or something that is tested. The thread from design to build, for the worked project:

Designed inBecomesChapters
the pages and wireframes (Chapter 27)the five HTML pages, one stylesheet and the page scripts40, 41
the layers and the middleware pipeline (Chapter 28)the Express application, its middleware and its routes42
the state machine and the rules (Chapters 23, 28)the rules module: the slots, the moves, the totals, the clock43
the schema (Chapter 29)the database, the connection pool and the store's queries44
ADR-5 and the transaction's sequence diagram (Chapters 22, 36)placing an order in one transaction that cannot oversell45
the security design, S1 to S17 (Chapter 31)password hashing, sessions, roles and guards46
the API's validation rules (Chapter 30)the validation module47
one shape for every error (Chapter 30)the error types and the error handler48
the SRS's verification column (Chapter 11)the tests, their cases and their reports50 to 56, 63 to 65
the deployment diagram (Chapter 25)the lab desktop configured: the environment file, the service, Nginx57, 60
ADR-1's Android wrapper (Chapter 36)the WebView app and its APK59

When the build shows that a design document was wrong, the document is corrected and given a new version, as the worked UML set was on 25 September (Chapter 35). The code and the documents describe one system for the whole semester, not only until the design review.

The order the worked team built it

The plan of Chapter 17, from the day after the design review:

TaskDatesWhat it producesOwner
F: Frontend11 Sep to 28 Septhe pages, the stylesheet and the scripts, the student's pages built first against a mock of the APISneha
B: Backend and database11 Sep to 24 Septhe application, its routes, services, rules and store; the database; placing an orderFarhan
V: Authentication, validation, errors25 Sep to 5 Octregistration and its password rules, staff accounts, the limit on failed sign-ins, the validation module, the error handlerRohan, with Farhan for the errors
Y: Deployment and APK6 Oct to 9 Octthe application on the lab desktop, and the APKRohan
T: Integration and system testing6 Oct to 12 Octthe routes and the database tested together; the whole system against the SRSRohan and Aditi
L: Load and security testing13 Oct to 15 Octthe lunch rush simulated; the security design tested line by lineRohan
R: Report, manual, presentation16 Oct to 26 Octthe final report, the user manual, the presentationAditi and Sneha
munotes.in247

The Build Phase at a Glance

Unit tests have no task of their own: they are written with the code they test, in B and V, which is where their hours fall. Around the tasks sit five dates that matter as much: the practice deployment on the lab desktop in week 8, 14 to 18 September, answering the one skill nobody had; the first increment review on Thursday 24 September; the guide's code review on Thursday 1 October; the second increment review on Friday 9 October, with the head cook; and the last day of work, Monday 26 October. Friday 2 October is a holiday.

Two increments, each one working

The tasks are grouped into the two increments of Chapter 15, and each ends in something the canteen can use:

Increment 1, 11 to 24 SeptemberIncrement 2, 25 September to 9 October
Requirementssigning in, with the seed accounts (FR-2); today's menu and stock (FR-4, FR-6); placing an order, its slot, its stock, its number and the one-order-per-slot rule (FR-7 to FR-11); my orders (FR-12); the counter's list and moving orders on (FR-14, FR-15)the counter's list refreshing itself, first of all (FR-14); registering (FR-1); staff accounts (FR-3); menu items (FR-5); cancelling (FR-13); the kitchen list (FR-16); the daily report (FR-17)
Alsothe database, the transaction, unit tests of the rulesthe password rules and the limit on failed sign-ins (NFR-6), the validation module, the error handler, deployment to the lab desktop, the APK

The first increment needed a signed-in student before anyone could order, so signing in came with it, using the accounts the setup script creates; registering new accounts waited for the second. FR-10, a Should, came early for the reason Chapter 15 gives: it is checked inside the same transaction as the stock rule.

The hours

Module 2's 120 person-hours, as the WBS shares them (Chapter 16):

WorkPackagesHours
4 The applicationfrontend 18, backend 16, database 10, authentication and validation 12, error handling 3, reviews and stand-ups 1271
5 Testingunit 8, integration 5, system and acceptance 5, load and security 422
6 Deploymentlocal hosting 2, the Linux server 4, the APK 4, the GitHub repository and release 212
7 Documentsthe report 8, the manual and screenshots 3, the presentation 415
Total120
munotes.in248

The Build Phase at a Glance

Four students at 30 hours each. Notice what is small: deployment is 12 hours, a rehearsal in week 8 included, not a panic in the last weekend; and the documents are 15, because most of the report is Module 1's documents, already written and already corrected.

How to read Module 2 of this book

Module 1 printed documents. Module 2 prints the application itself, and three rules make what it prints trustworthy:

  • Every file is the real file. Each listing marked with a file name is checked, character for character, against the worked application's source, so the book cannot show a line the application does not contain.
  • Every terminal session was run. Each one was run on Ubuntu 24.04, the system the worked team deploys to, against that exact code, and its output copied from the screen, not typed.
  • The application's tests pass. The whole suite runs on three versions of Node.js, 22, 24 and 26, before any session in this book can be run at all.

And the rule from Chapter 2 still holds: copy the method, never the project. Read what the worked team built and why, then build the same kind of thing for your own problem. An examiner who asks for a small change during your demonstration will find out at once whose code it is.

Do this for your project

  1. Put your build plan's dates in your plan, with your increment reviews and your guide's code review on fixed dates.
  2. Set up every member's machine in the first days, and create the repository on the first day (Chapters 39 and 61).
  3. Build in thin slices that each work end to end, Must Haves first, and show each one to your users.
  4. Write the tests with the code, not after it.
  5. Deploy once early, as a rehearsal, long before it counts.
  6. When the build shows a design document is wrong, correct the document and give it a new version.
  7. Keep the last two weeks for testing at scale, the report and the presentation.

Mistakes that cost marks

Everything built, then everything tested, in the last week, when nothing can be fixed any more.

The first deployment on the day of the demonstration. It fails for a reason nobody has seen before, in front of the examiner.

One member writing all the code. The repository's history shows exactly who did, and the viva asks the others.

munotes.in249

The Build Phase at a Glance

Code and design that drifted apart without anyone noticing, so the report describes a system that was never built.

Documentation written from memory the night before, with screenshots of a version that no longer exists.

Quick revision

  • Module 2: Implementation, Testing, Deployment & Evaluation Phase, 30 hours per student.
  • Five rows: application development, integration and system testing, deployment (with GitHub, mandatory), performance and security testing, final documentation.
  • Four deliverables: working application, GitHub repository, final report, presentation and demonstration.
  • Every Module 1 document becomes code or tests; when the build proves one wrong, it gets a new version.
  • Build in increments that each work; tests with the code; deploy early as a rehearsal.

Questions you must be able to answer

1. What does MU's Module 2 cover, and what are its deliverables? Application development (frontend and backend implementation, database integration, authentication and validation, error handling), integration and system testing (unit, black-box and integration testing, test case preparation, bug tracking), deployment (cloud deployment or local hosting, an APK build, server configuration where applicable, and GitHub, which is mandatory), performance and security testing, and final documentation. Its deliverables are the working application, the GitHub repository, the final report, and the presentation and demonstration.

2. What does "Cloud deployment / Local hosting" in MU's syllabus mean for a project? That either is acceptable: the application may be put on a cloud server or hosted on a local machine. The worked team hosts its trial locally, on the college lab's desktop, because its constraints require the college's machine and network.

3. How do the Module 1 documents relate to the Module 2 work? Each becomes something built or tested: the pages and wireframes become the frontend, the schema becomes the database, the API specification becomes the routes and their validation, the security design becomes the authentication code and its tests, and the SRS's verification column becomes the test plan. When the build shows a document to be wrong, the document is corrected and versioned.

4. Why did the worked team build in two increments rather than all at once? So that each increment ended in something that worked and could be shown to the canteen: after the first, students could already order and the counter could serve, and the review of it found a change, the counter's list refreshing itself, early enough to make before anything was built on top of it.

5. Why write unit tests during the build rather than after it? Because a test written with the code catches a mistake while the code is fresh and nothing depends on it yet, and because tests written at the end, under time pressure, are the first work to be cut.

Contents This chapter on its own page

munotes.in250

Chapter Thirty-Nine

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

Syllabus topic Module 2, "Application Development", the ground it stands on: the machine every part of the application is built and run on.

In one line

Before the first line of the application is written, every member's machine needs the same tools at the same versions, Node.js with npm to run the server, MySQL for the data, Git to record every change and an editor to write in, and the project needs one home: a repository on GitHub that everyone clones, with a package.json that names the project and pins its libraries.

In the wording to use when asked: a development environment is the set of tools, runtimes and configuration on which software is built and run; for a team it must be reproducible, with the same runtime and database versions on every machine as on the server, dependencies pinned in a manifest and a lock file, secrets kept out of version control, and a shared repository as the single source of the code.

Why every machine must match

A program that works on one laptop and fails on another almost always meets a different version of something: a newer Node.js, an older MySQL, a library that moved on. The fix is not to debug each machine but to make them the same, and the worked team made three rules on the first day of the build:

  1. The same Node.js line everywhere: 24, the one the server runs.
  2. The same MySQL everywhere as on the server: 8.0.46, the version Ubuntu 24.04 installs on the lab desktop. Testing against anything else tests a different system.
  3. The same libraries, to the last digit: exact versions in package.json, and every version of every library underneath them recorded in package-lock.json, which is committed.

The tools, and which versions

ToolWhat it is forThe version, and why
Node.jsruns the server, the tests and the team's scripts24, a long-term support line, supported until 30 April 2028, beyond the examination. NFR-12 allows 22 or later, and the application's tests are run on 22, 24 and 26
npminstalls the libraries and runs the project's commandswhichever comes with Node.js: 11.19.0 with Node.js 24.21.0
MySQLstores the data8.0.46, the lab desktop's version (next section)
Gitrecords every change to every fileany current version; Ubuntu 24.04 installs 2.43.0
An editorwriting the codeVisual Studio Code, free for Windows, macOS and Linux, the team's choice in Chapter 18

In September 2026 the newest Node.js 24 release was 24.21.0, of 7 September. Take the newest release of the line, not an exact number from a book: a later 24.x has the same features and more fixes. Ubuntu's own nodejs package is version 18, which reached its end on 30 April 2025, so the lab desktop gets Node.js from nodejs.org instead.

munotes.in251

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

MySQL: 8.0, 8.4 or 9.7?

MySQL's download page offered four versions on 30 September 2026: 26.7.0, 9.7.2 LTS, 8.4.11 LTS and 8.0.46. On 21 April 2026 Oracle moved MySQL 8.0 to what it calls Sustaining Support, and its notice encourages users to upgrade to 8.4 LTS or 9.7 LTS.

So why does the worked team use 8.0? Because its server is fixed: the lab desktop runs Ubuntu 24.04, and Ubuntu's own package of MySQL is 8.0, which Ubuntu's security team keeps patching. Its updates of 19 June and 26 August 2026 each fixed security issues in it. A laptop that runs the same 8.0.46 tests exactly what the server will run. That is the rule that decides it, and it is recorded in ADR-1 (Chapter 36).

For your own project: if your server's version is fixed, as the worked team's is, install that version everywhere. If it is not, choose 8.4 LTS. This book's application has been run on two lines, 8.0.46 on Ubuntu and 9.6.0 on macOS, and its whole test suite passes on both; 8.4 lies between them but has not been run by this book.

Installing on each system

The worked team had three Windows 11 laptops, one MacBook and the Ubuntu desktop in the lab (Chapter 6). The steps for each follow. Only the Ubuntu ones are run in this book; the others are described from the installers' own download pages.

Windows 11

  1. Node.js: from nodejs.org, the Windows installer for the 24 LTS line, node-v24.21.0-x64.msi in September 2026 (an arm64.msi exists for Windows on ARM). Accept the defaults, which put node and npm on the path. Close and reopen any terminal afterwards: a terminal opened before the install does not see them.
  2. Git: Git for Windows, from git-scm.com, with its defaults.
  3. MySQL: the MySQL Installer for Windows, version 8.0.46, from MySQL's download pages. It is the last series that has this installer; later versions of MySQL are installed from their own MSI or ZIP. Choose the server, set a password for the root account and keep it safe, and let it run MySQL as a Windows service, so that it starts with the laptop.
  4. Visual Studio Code, from code.visualstudio.com.

macOS

  1. Node.js: the macOS installer, node-v24.21.0.pkg, from nodejs.org.
  2. Git: Apple's Command Line Tools include it. Typing git in the Terminal offers to install them if they are missing.
  3. MySQL: the 8.0.46 disk image for the Mac's processor, Apple silicon (ARM) or Intel (x86). The installer asks for a root password.
  4. Visual Studio Code, from code.visualstudio.com.

Ubuntu 24.04

MySQL and Git come from Ubuntu's own archive:

sudo apt update
sudo apt install -y mysql-server git curl

Node.js comes from nodejs.org, checked against the release's own list of checksums before it is used, which is how the book's lab machine is built. For a PC with an Intel or AMD processor:

munotes.in252

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

cd /tmp
curl -fsSLO https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.xz
curl -fsSL https://nodejs.org/dist/v24.21.0/SHASUMS256.txt | grep " node-v24.21.0-linux-x64.tar.xz$" | sha256sum -c -
sudo mkdir -p /opt/node-24
sudo tar -xJf node-v24.21.0-linux-x64.tar.xz -C /opt/node-24 --strip-components=1
echo 'export PATH=/opt/node-24/bin:$PATH' >> ~/.bashrc

The third line must print OK. If it prints FAILED, the file is not the one the Node.js project published: delete it and download it again.

Checking what you installed

On every machine, in a new terminal, each tool must answer with its version:

$ node --version
v24.21.0
$ npm --version
11.19.0
$ git --version
git version 2.43.0
$ mysql --version
mysql  Ver 8.0.46-0ubuntu0.24.04.4 for Linux on aarch64 ((Ubuntu))

This is the lab machine, whose processor is ARM, so MySQL says aarch64; a PC says x86_64. The client answering proves the client is installed. The server must answer too:

$ sudo mysql -e "SELECT VERSION();"
VERSION()
8.0.46-0ubuntu0.24.04.4

On Ubuntu the MySQL administrator, root, is let in by the operating system when the command is run with sudo, so no password is asked for. On Windows and macOS, mysql -u root -p asks for the password set during installation.

Then tell Git who you are, once per machine, with the name and email that will appear on every commit:

$ git config --global user.name "Aditi Kulkarni"
$ git config --global user.email "aditi@college.example"
$ git config --global init.defaultBranch main
$ git config --global user.name
Aditi Kulkarni
$ git config --global user.email
aditi@college.example

Use your real name and the email address of your GitHub account. The repository's history is read by your guide and your examiner, and a commit by "user" from "user@laptop" tells them nothing (Chapter 72).

Visual Studio Code

The team chose Visual Studio Code because it is free, runs on all three systems, and understands JavaScript and HTML without anything added. Four of its features carry the work:

  • Open the whole project folder, not single files, so that the editor sees every file and the search covers the project.
  • The built-in terminal, opened from the View menu, starts in the project folder, which is where every command in this book is typed.
  • The Source Control panel shows which files have changed since the last commit, and what changed in each.
  • Search across files, which answers "where is this function used?" in a second.

No extension is needed for anything in this book. If your team adds one, such as a formatter, agree on it and on its settings together, or every commit will reformat every file.

munotes.in253

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

The repository, from the first day

MU makes "Version control using GitHub" mandatory, and the worked team took that to mean from the start, not at the end. Aditi created the repository on GitHub on the project's first day, 27 July, while choosing the problem: private, with GitHub's option to start it with a README, which made its first commit, and the other three members added as collaborators. Everything the team wrote as text went into it from then on: the plan's Gantt and WBS sources in the second week, the diagrams' sources in August, the schema in September. That is why the repository already held docs/ when the build began.

On 11 September each member cloned it onto their own machine, and Farhan made the first commit of the application itself. The commands, with your own account and repository in the address:

git clone https://github.com/YOUR-ACCOUNT/canteen-preorder.git
cd canteen-preorder
npm init -y
npm install --save-exact express@5.2.1 mysql2@3.24.5
git add package.json package-lock.json .gitignore
git commit -m "Start the application: package.json, express and mysql2"
git push

Chapter 61 explains what each Git command does, and Chapter 62 how four people share one repository without overwriting each other.

The package.json that starts the project

npm init -y writes a first package.json without asking any questions. In an empty folder:

$ mkdir ~/first-steps && cd ~/first-steps
$ npm init -y
Wrote to /home/student/first-steps/package.json:

{
  "name": "first-steps",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs"
}

Then npm install adds the two libraries the architecture chose, at exactly the versions ADR-1 names. --save-exact writes 5.2.1 and not ^5.2.1, a range that would let a later npm install take a newer version without anyone deciding to:

$ npm install --save-exact express@5.2.1 mysql2@3.24.5

added 78 packages in 4s
$ cat package.json
{
  "name": "first-steps",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "express": "5.2.1",
    "mysql2": "3.24.5"
  }
}

Two things were written besides package.json. The folder node_modules holds the libraries, and the libraries they need in turn, dozens of packages in all. And package-lock.json records the exact version of every one of those packages, so that anyone who installs from it gets the same set.

Commit package.json and package-lock.json; never commit node_modules. It can always be made again from the lock file, and it would fill the repository with other people's code. The team's .gitignore, the list of what Git must never record, says so on its first line:

# Installed packages: npm install recreates them.
node_modules/

# Secrets and local settings. Commit .env.example instead.
.env

# Logs and the files editors and operating systems leave.
*.log
.DS_Store
Thumbs.db
.vscode/
.idea/

# Android build output and machine-specific settings.
android/.gradle/
android/build/
android/app/build/
android/local.properties
*.keystore
*.jks
munotes.in254

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

The second entry matters as much: .env holds the database password, and a password committed once stays in the history for ever (Chapter 31). The Android lines anticipate Chapter 59.

The worked project's package.json

This is how the file stands at the end of the project. It began as the file npm init wrote. The team gave it a description, marked it private, pointed main at the server, deleted the empty fields and the placeholder test command, and added each script with the chapter that needed it:

{
  "name": "canteen-preorder",
  "version": "1.0.0",
  "description": "Order canteen lunch before the break.",
  "private": true,
  "main": "src/server.js",
  "engines": {
    "node": ">=22"
  },
  "scripts": {
    "start": "node src/server.js",
    "dev": "node --watch src/server.js",
    "db:setup": "node scripts/setup-db.js",
    "test": "node --test --test-concurrency=1 --test-reporter=./test/reporter.js \"test/**/*.test.js\"",
    "test:unit": "node --test --test-reporter=./test/reporter.js test/unit/*.test.js",
    "layers": "node scripts/check-layers.js",
    "load": "node scripts/load-test.js",
    "race": "node scripts/race-test.js"
  },
  "dependencies": {
    "express": "5.2.1",
    "mysql2": "3.24.5"
  }
}
  • "private": true stops the project being published to the npm registry by accident.
  • No "type". npm init wrote "type": "commonjs", and the team removed it: the server's files use require(), the CommonJS style, which is what Node.js assumes when package.json names no type. The pages' scripts, which use import, run in the browser and are not affected.
  • "engines" records NFR-12's rule, Node.js 22 or later, where npm and every reader can see it.
  • "dependencies": two libraries, at exact versions. Everything else the application uses is built into Node.js.
  • The scripts are the project's commands, so that nobody has to remember a long one: npm start runs the server (Chapter 42); npm run dev runs it and restarts it whenever one of its files is saved; npm run db:setup builds the database afresh (Chapter 44); npm test runs every test, one file at a time because the integration tests share one test database (Chapter 50); npm run test:unit runs only the fast ones; npm run layers checks the architecture document's layer rule (Chapter 49); and npm run load and npm run race are the load test and the race test (Chapters 63 and 45).

Checking a machine: the finished project runs

NFR-12 promises that the application installs on Windows, macOS and Linux with Node.js 22 or later and MySQL 8.0 or later, and says how that is proved: by installing it on the team's machines. On each machine the check is the same six steps, which are also the start of the README (Chapter 69): install the libraries from the lock file, create the settings file, create the database and its account, build the tables, start the server, and ask it whether it is well.

munotes.in255

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

$ cd ~/canteen-preorder
$ npm ci

added 78 packages in 886ms
$ cp .env.example .env
$ sudo mysql < sql/create-database.sql
$ npm run db:setup

> canteen-preorder@1.0.0 db:setup
> node scripts/setup-db.js

Database "canteen" is ready.
$ npm start > server.log 2>&1 &
$ curl -s --retry 10 --retry-connrefused localhost:3000/api/health; echo
{"status":"ok","database":"ok"}
  • npm ci, not npm install: it installs exactly what the lock file says, and fails rather than change it. It is the command for every machine except the one where a library is being added.
  • cp .env.example .env makes the settings file from the committed example. Its database password must match the one in sql/create-database.sql; on a real server, change both.
  • sudo mysql < sql/create-database.sql runs the script as the MySQL administrator: it makes the canteen and canteen_test databases and the account the application uses (Chapter 44).
  • The server is started in the background, its output kept in server.log, and curl asks the health endpoint, retrying until the server is listening. {"status":"ok","database":"ok"} means the server runs and can reach its database.

Step 4 does two things: it builds the tables from sql/schema.sql (Chapter 29) and then fills them from sql/seed.sql, which is the demonstration data every later chapter signs in with:

-- Canteen Pre-order: demonstration data.
-- Every account below has the password canteen-demo. They
-- exist for development and testing only: delete them, or
-- change every password, before real students use the site.

INSERT INTO users (id, name, email, role, password_hash) VALUES
(1, 'Lata Pawar', 'owner@college.example', 'owner',
 'scrypt$16384$8$5$ksghOrQ3EGdbCgLFIcsFsg==$y00HfuQcWVieFlhxkyOc7B2Jg3NbwXfnvzXPVFbwbjs='),
(2, 'Ganesh More', 'counter@college.example', 'staff',
 'scrypt$16384$8$5$SSxy/3/mSE/wpVZzyyCiUg==$ALtZp9v15MukcZTYLvPnLtCnl+2iYsAl/3HdddZgEG8='),
(3, 'Priya Menon', 'priya@college.example', 'student',
 'scrypt$16384$8$5$KS08D452kUu5TZgIo1oqvQ==$9qSU6xIZWSEEoOTT0M/aTErZMi3F6/m/576W/0tNmuc='),
(4, 'Kabir Singh', 'kabir@college.example', 'student',
 'scrypt$16384$8$5$W4SF4swS6HKxJrX+RIMPJw==$uqAiB2GFY5RxZhYw+epSy2KWH3gZ+g3s2lOCmYjQiVE='),
(5, 'Ananya Rao', 'ananya@college.example', 'student',
 'scrypt$16384$8$5$VwHChMSPy5ShNRuq24+/oA==$iOIOXeFb2NTbMIS636egd6ryQtKtt5V6JqOFgoTe9uA='),
(6, 'Yusuf Khan', 'yusuf@college.example', 'student',
 'scrypt$16384$8$5$pJUEvwQnRmhOSG2HQsrEww==$SS7XgRCdPgooAMtPG7S018OJCNMYCRX9klbDvic4GFY=');

-- Prices in paise: 7000 is Rs 70.00.
INSERT INTO menu_items
  (id, name, category, price_paise, is_veg, is_available,
   stock_left) VALUES
( 1, 'Veg Thali',       'meals',    7000, TRUE,  TRUE,   60),
( 2, 'Veg Biryani',     'meals',    8000, TRUE,  TRUE,   40),
( 3, 'Chicken Biryani', 'meals',   11000, FALSE, TRUE,   30),
( 4, 'Pav Bhaji',       'meals',    6000, TRUE,  TRUE,   50),
( 5, 'Chole Bhature',   'meals',    7000, TRUE,  FALSE,   0),
( 6, 'Vada Pav',        'snacks',   2000, TRUE,  TRUE,  120),
( 7, 'Samosa',          'snacks',   2000, TRUE,  TRUE,  100),
( 8, 'Masala Dosa',     'snacks',   5000, TRUE,  TRUE,   40),
( 9, 'Idli Sambar',     'snacks',   4000, TRUE,  TRUE,   40),
(10, 'Veg Sandwich',    'snacks',   4000, TRUE,  TRUE,   40),
(11, 'Cutting Chai',    'drinks',   1200, TRUE,  TRUE,  150),
(12, 'Filter Coffee',   'drinks',   2000, TRUE,  TRUE,   80),
(13, 'Lime Juice',      'drinks',   2500, TRUE,  TRUE,   60),
(14, 'Gulab Jamun',     'desserts', 3000, TRUE,  TRUE,   50);

Three things about that file matter more than its contents. Its first lines are a warning, because accounts with a published password must never reach real students. The prices are in paise, so 7000 is Rs 70 (ADR-2). And the data is believable: real dish names, real prices, one dish switched off for the day. That is what makes a demonstration look finished (Chapter 71), and it costs nothing to do at the start rather than the end.

munotes.in256

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

On Windows, two things differ. PowerShell does not accept < to feed a file to a command: run mysql -u root -p < sql\create-database.sql in the Command Prompt instead. And instead of starting the server in the background, run npm start in one terminal and open http://localhost:3000/api/health in a browser.

The worked team ran this check on the three Windows laptops and the MacBook when the second increment began, and on the lab desktop at the practice deployment in week 8. Every run is recorded in the test report (Chapter 55).

Do this for your project

  1. Agree on one version of each tool, matching your server where it is fixed, and write the versions down.
  2. Install Node.js from nodejs.org, not from an old package archive, and check each download against its published checksum on Linux.
  3. Install MySQL, keep the administrator's password safe, and check that the server answers.
  4. Set your Git name and email to your real name and your GitHub address.
  5. Create the repository on GitHub on your first day, and put your plans and diagrams in it from then on.
  6. Start the application with npm init, add each library with --save-exact, and commit package.json, package-lock.json and a .gitignore that excludes node_modules and .env.
  7. On every machine, run the six-step check before building anything else, and again whenever someone's machine misbehaves.

Mistakes that cost marks

"It works on my machine." Four laptops with four versions of Node.js and three of MySQL, and a demonstration that fails on the one that matters.

node_modules committed. Thousands of files of other people's code in the repository, and an examiner who opens it first.

.env committed, with the database password in the history for ever.

Libraries installed without exact versions and without a lock file, so the build that worked in September breaks in October on a library nobody changed.

The repository created in the last week, with the whole project in one commit: the history an examiner reads says the work was not done in it.

Commits by "user" from "user@laptop", because nobody set their name.

Quick revision

  • Every machine the same: one Node.js line (24), the server's MySQL (8.0.46), libraries at exact versions with a committed lock file.
  • MySQL 8.0 moved to Oracle's Sustaining Support on 21 April 2026; Ubuntu 24.04 still patches its 8.0; otherwise choose 8.4 LTS.
  • Check each tool with --version, and the server with sudo mysql -e "SELECT VERSION();".
  • git config --global user.name and user.email once per machine.
  • The repository from day one; npm init, npm install --save-exact, commit package.json, package-lock.json, .gitignore; never node_modules or .env.
  • npm ci installs from the lock file; the six-step check proves a machine (NFR-12).
munotes.in257

Setting Up the Development Machine: Node.js, MySQL, an Editor and Git

Questions you must be able to answer

1. Why should every member of a team use the same versions of Node.js and MySQL as the server? Because a difference in version is the most common reason a program works on one machine and fails on another. With the same versions everywhere, a test that passes on a laptop tests what the server will run, and a failure on one machine is a real failure, not a difference of setup.

2. What is the difference between package.json and package-lock.json? package.json names the project, its commands and the libraries it depends on directly, with the versions the team chose. package-lock.json records the exact version of every package installed, including the libraries those libraries depend on, so that every installation from it produces the same set. Both are committed.

3. What is the difference between npm install and npm ci? npm install adds or updates packages and may change the lock file. npm ci installs exactly what the lock file records, removes anything else, and fails if the lock file and package.json disagree, which makes it the right command for every machine that only needs to run the project.

4. Why are node_modules and .env listed in .gitignore? node_modules can always be recreated from the lock file and would fill the repository with other people's code. .env holds secrets such as the database password, and anything committed stays in the repository's history even after it is deleted.

5. The worked team uses MySQL 8.0, which Oracle moved to Sustaining Support in April 2026. Why, and what would you choose? Because its server is fixed: the lab desktop runs Ubuntu 24.04, whose own MySQL package is 8.0 and still receives Ubuntu's security updates, and every laptop runs the same version so that the tests match the server. A project whose server version is not fixed should choose 8.4 LTS.

6. What does --save-exact do, and why did the team use it? It records the exact version installed, such as 5.2.1, instead of a range such as ^5.2.1. With a range, a later installation could take a newer version that nobody chose or tested; with an exact version, a change of library is always a deliberate commit.

Contents This chapter on its own page

munotes.in258

Chapter Forty

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

Syllabus topic Module 2, "Application Development: Frontend implementation", first part: the pages, the stylesheet and the module that calls the API.

In one line

The frontend is what a student, the counter and the owner actually touch: a handful of HTML pages that say what is on each screen, one stylesheet that says how it looks on a phone first and on a wide screen second, and one small module through which every page talks to the server, sending and receiving JSON and turning every refusal into a message the page can show.

In the wording to use when asked: frontend implementation turns the screen design into markup, styles and scripts: semantic HTML for structure and accessibility, CSS for presentation with a mobile-first responsive layout, and client-side JavaScript that calls the backend's API through a single request layer which serialises JSON, carries the session, and maps error responses to a consistent client-side error type.

What the frontend is made of

Everything the browser downloads lives in the project's public/ folder, and the same Express server that answers the API hands it out (Chapter 28):

FileWhat it is
index.htmlsigning in and registering
menu.htmltoday's menu and the order being put together (printed in Chapter 27)
orders.htmlthe student's orders today
counter.htmlthe counter's list, and what the kitchen still has to make
owner.htmlthe menu, today's stock, the day's report, staff accounts
404.htmlthe page for an address that does not exist
favicon.svgthe small picture in the browser's tab
css/app.cssthe one stylesheet
js/api.jsthe only code that talks to the server
js/mock.jsa pretend server, for building pages before the real one answered
js/ui.js and one script per pageChapter 41

The pages

Every page has the same anatomy: the same <head>, a bar across the top with the product's name and, once someone is signed in, the navigation and a Sign out button; a <main> holding one heading, a message area, and the page's own content; and one script, loaded as a JavaScript module. The sign-in page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sign in | Canteen Pre-order</title>
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="/css/app.css">
  <script type="module" src="/js/signin.js"></script>
</head>
<body>
  <header class="bar">
    <span class="brand">Canteen Pre-order</span>
  </header>
  <main class="page narrow">
    <h1>Order lunch before the break</h1>
    <p class="lead">Choose your food and a pickup time. Collect
      it from the counter and pay there.</p>
    <p class="message" id="message" role="alert" hidden></p>

    <form id="signin-form" class="card">
      <h2>Sign in</h2>
      <label for="signin-email">College email</label>
      <input id="signin-email" name="email" type="email"
        autocomplete="username" required>
      <label for="signin-password">Password</label>
      <input id="signin-password" name="password" type="password"
        autocomplete="current-password" required>
      <button type="submit">Sign in</button>
    </form>

    <form id="register-form" class="card">
      <h2>New here? Create an account</h2>
      <label for="register-name">Your name</label>
      <input id="register-name" name="name" autocomplete="name"
        required minlength="2" maxlength="80">
      <label for="register-email">College email</label>
      <input id="register-email" name="email" type="email"
        autocomplete="email" required maxlength="120">
      <label for="register-password">Password: 8 or more
        characters, and not one you use anywhere else</label>
      <input id="register-password" name="password" type="password"
        autocomplete="new-password" required minlength="8"
        maxlength="128">
      <button type="submit">Create account</button>
    </form>
  </main>
</body>
</html>
munotes.in259

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

What each part is for:

  • <meta name="viewport"> tells a phone to lay the page out at its own width. Without it, a phone pretends to be a desktop and shrinks the page to an unreadable size.
  • <script type="module"> loads the page's script as a module: it can import the shared modules, and it runs after the page has been read, so it can find every element.
  • Every field has a <label> tied to it by for and id (NFR-8): tapping the label focuses the field, and a screen reader says what the field is for.
  • autocomplete tells the browser and password managers what each field holds: username and current-password to fill in a saved sign-in, new-password to offer to make a strong one. OWASP's verification standard asks that password managers are not blocked, and these values are what let them work.
  • required, minlength and maxlength repeat the server's own rules, so a mistake is caught before the request is sent. They are for the user's convenience only: the server checks everything again (Chapter 47).
  • role="alert" on the message area makes a screen reader read a message the moment it appears. It starts hidden and the script shows it.
  • The password label carries the advice security control S5 asks for, a password used nowhere else, in the label itself so it is read whenever the field is (Chapter 46).

The student's orders page is the shortest:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My orders | Canteen Pre-order</title>
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="/css/app.css">
  <script type="module" src="/js/orders.js"></script>
</head>
<body>
  <header class="bar">
    <span class="brand">Canteen Pre-order</span>
    <nav>
      <a href="/menu.html">Menu</a>
      <a href="/orders.html" aria-current="page">My orders</a>
    </nav>
    <span class="who" id="who"></span>
    <button type="button" class="link" id="signout">Sign out</button>
  </header>
  <main class="page narrow">
    <h1>My orders today</h1>
    <p class="hint">This page updates itself every 15 seconds.</p>
    <p class="message" id="message" role="alert" hidden></p>
    <div id="orders" aria-live="polite">
      <p>Loading your orders...</p>
    </div>
  </main>
</body>
</html>

aria-current="page" marks the link to the page the user is on, for screen readers and for the stylesheet. aria-live="polite" makes a screen reader announce the orders when they change, without interrupting what it is reading. The page says in words that it updates itself (FR-12), so nobody waits for a button that is not there.

The counter's page puts the list of orders beside what the kitchen still has to make:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Counter | Canteen Pre-order</title>
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="/css/app.css">
  <script type="module" src="/js/counter.js"></script>
</head>
<body>
  <header class="bar">
    <span class="brand">Canteen Pre-order</span>
    <nav>
      <a href="/counter.html" aria-current="page">Counter</a>
      <a href="/owner.html" id="owner-link" hidden>Owner</a>
    </nav>
    <span class="who" id="who"></span>
    <button type="button" class="link" id="signout">Sign out</button>
  </header>
  <main class="page with-side">
    <section class="main-column">
      <h1>Orders at the counter</h1>
      <label for="slot">Pickup slot</label>
      <select id="slot">
        <option value="">All slots</option>
      </select>
      <p class="message" id="message" role="alert" hidden></p>
      <div id="orders" aria-live="polite">
        <p>Loading orders...</p>
      </div>
    </section>
    <section class="card side-column">
      <h2>Still to make</h2>
      <p class="hint" id="kitchen-slot">Choose a slot to see
        what the kitchen still has to make for it.</p>
      <table id="kitchen" hidden>
        <thead><tr><th>Item</th><th>Quantity</th></tr></thead>
        <tbody></tbody>
      </table>
    </section>
  </main>
</body>
</html>
munotes.in260

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

The Owner link starts hidden: the owner is a kind of counter staff (Chapter 20) and uses this page too, and the script shows the link only when the owner is signed in. Hiding a link is a convenience, not security: the owner's page and its API refuse anyone else (Chapter 46). The kitchen list is a real <table>, because it is tabular data, with a header row a screen reader can announce.

The owner's page is the longest, because the owner does the most kinds of thing:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Owner | Canteen Pre-order</title>
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="/css/app.css">
  <script type="module" src="/js/owner.js"></script>
</head>
<body>
  <header class="bar">
    <span class="brand">Canteen Pre-order</span>
    <nav>
      <a href="/counter.html">Counter</a>
      <a href="/owner.html" aria-current="page">Owner</a>
    </nav>
    <span class="who" id="who"></span>
    <button type="button" class="link" id="signout">Sign out</button>
  </header>
  <main class="page">
    <h1>The canteen today</h1>
    <p class="message" id="message" role="alert" hidden></p>

    <section>
      <h2>Menu and today's stock</h2>
      <p class="hint">Set each item's stock every morning.</p>
      <div id="menu-list"><p>Loading the menu...</p></div>
    </section>

    <form id="add-item" class="card">
      <h2>Add an item</h2>
      <label for="item-name">Name</label>
      <input id="item-name" name="name" required minlength="2"
        maxlength="60">
      <label for="item-category">Category</label>
      <select id="item-category" name="category" required>
        <option value="meals">Meals</option>
        <option value="snacks">Snacks</option>
        <option value="drinks">Drinks</option>
        <option value="desserts">Desserts</option>
      </select>
      <label for="item-price">Price in rupees</label>
      <input id="item-price" name="price" type="number" min="1"
        max="1000" step="0.5" required>
      <label class="check"><input type="checkbox" name="isVeg"
        checked> Vegetarian</label>
      <button type="submit">Add item</button>
    </form>

    <section class="card">
      <h2>Today's report</h2>
      <div id="report"><p>Loading the report...</p></div>
    </section>

    <form id="add-staff" class="card">
      <h2>Add a counter staff account</h2>
      <label for="staff-name">Name</label>
      <input id="staff-name" name="name" required minlength="2"
        maxlength="80">
      <label for="staff-email">Email</label>
      <input id="staff-email" name="email" type="email" required>
      <label for="staff-password">Password, 8 or more
        characters</label>
      <input id="staff-password" name="password" type="password"
        autocomplete="new-password" required minlength="8"
        maxlength="128">
      <button type="submit">Add account</button>
    </form>
  </main>
</body>
</html>

The owner types a price in rupees, as people think of prices, with step="0.5" allowing half-rupees; the script turns it into paise before sending it, because the API and the database hold only whole paise (ADR-2). The checkbox sits inside its label, which is a second correct way to tie a label to a field.

The last page is the one nobody should need, served for any address that does not exist:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page not found | Canteen Pre-order</title>
  <link rel="stylesheet" href="/css/app.css">
</head>
<body>
  <main class="page narrow">
    <h1>Page not found</h1>
    <p>There is no page at this address.</p>
    <p><a href="/">Go to the sign-in page</a></p>
  </main>
</body>
</html>
munotes.in261

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

It has no script: a page shown because something went wrong should depend on as little as possible. And the icon in the browser's tab is five lines of SVG, a bowl on the brand colour, drawn as text so that it is sharp at every size:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <rect width="32" height="32" rx="6" fill="#9a3412"/>
  <path d="M9 19h14a7 7 0 0 1-14 0z" fill="#fff"/>
  <path d="M8 17h16" stroke="#fff" stroke-width="2"/>
</svg>

The stylesheet

One stylesheet serves every page. It is written for a phone first: everything outside the one media query at the end is the phone's layout, and the media query only adds a second column when the screen is at least 48rem wide. The whole file:

/* Canteen Pre-order: one stylesheet for every page.
   Written for a phone first; the rules inside the media
   query at the end only add a second column on a wide
   screen. Colours are variables, so they change in one
   place, and every text colour meets a contrast of at
   least 4.5 to 1 against the background it sits on. */

:root {
  --ink: #1c1917;
  --muted: #57534e;
  --paper: #ffffff;
  --surface: #f5f1ea;
  --line: #d6d3d1;
  --brand: #9a3412;
  --brand-dark: #7c2d12;
  --veg: #166534;
  --nonveg: #9f1239;
  --error: #b91c1c;
  --ok: #166534;
  --radius: 8px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto,
    sans-serif;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--surface);
}

h1 {
  font-size: 1.5rem;
  margin: 0.5rem 0 1rem;
}

h2 {
  font-size: 1.15rem;
  margin: 0 0 0.75rem;
}

a {
  color: var(--brand-dark);
}

/* The bar across the top of every page. */
.bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.75rem 1rem;
  background: var(--brand);
  color: #fff;
}

.bar .brand {
  font-weight: 700;
  margin-right: auto;
}

.bar nav {
  display: flex;
  gap: 1rem;
}

.bar a,
.bar .link {
  color: #fff;
}

.bar a[aria-current="page"] {
  font-weight: 700;
  text-decoration-thickness: 2px;
}

.who {
  font-size: 0.9rem;
}

.page {
  max-width: 64rem;
  margin: 0 auto;
  padding: 1rem;
}

.page.narrow {
  max-width: 34rem;
}

/* The counter's slot chooser, above the list of orders. */
.main-column > select {
  margin-bottom: 1rem;
}

.lead {
  font-size: 1.1rem;
}

.hint {
  color: var(--muted);
  font-size: 0.9rem;
}

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 1rem;
}

/* Forms: one field under another, big enough to tap. */
label {
  display: block;
  font-weight: 600;
  margin: 0.75rem 0 0.25rem;
}

label.check {
  font-weight: 400;
}

input,
select,
button {
  font: inherit;
}

input:not([type="checkbox"]),
select {
  width: 100%;
  padding: 0.6rem;
  border: 1px solid #78716c;
  border-radius: var(--radius);
  background: var(--paper);
}

button {
  cursor: pointer;
  border: 0;
  border-radius: var(--radius);
  padding: 0.65rem 1.1rem;
  background: var(--brand);
  color: #fff;
  font-weight: 600;
}

form > button {
  margin-top: 1rem;
  width: 100%;
}

button:hover {
  background: var(--brand-dark);
}

button:disabled {
  background: #a8a29e;
  color: var(--ink);
  cursor: not-allowed;
}

button.link {
  background: none;
  padding: 0;
  text-decoration: underline;
  font-weight: 400;
}

button.quiet {
  background: var(--paper);
  color: var(--brand-dark);
  border: 1px solid var(--brand-dark);
}

:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 2px;
}

/* Messages to the user, and errors beside a field. */
.message {
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  background: #fee2e2;
  color: #7f1d1d;
}

.message.ok {
  background: #dcfce7;
  color: #14532d;
}

.field-error {
  color: var(--error);
  font-size: 0.9rem;
  margin: 0.25rem 0 0;
}

[aria-invalid="true"] {
  border-color: var(--error);
  border-width: 2px;
}

/* The menu: one row per item. */
.category {
  margin-bottom: 1.5rem;
}

.item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 0.5rem;
}

.item .about {
  flex: 1;
}

.item .name {
  font-weight: 600;
}

.item .note {
  color: var(--muted);
  font-size: 0.9rem;
}

.item.off .name {
  color: var(--muted);
}

.stepper {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.stepper button {
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  font-size: 1.25rem;
}

.stepper output {
  min-width: 1.5rem;
  text-align: center;
  font-weight: 700;
}

/* Small labels: vegetarian or not, and an order's status. */
.badge {
  display: inline-block;
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  color: #fff;
  background: var(--muted);
}

.badge.veg {
  background: var(--veg);
}

.badge.nonveg {
  background: var(--nonveg);
}

.badge.placed {
  background: #1d4ed8;
}

.badge.preparing {
  background: #6b21a8;
}

.badge.ready {
  background: var(--ok);
}

.badge.no_show {
  background: var(--nonveg);
}

.lines {
  list-style: none;
  padding: 0;
  margin: 0;
}

.lines li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.25rem 0;
  border-bottom: 1px dashed var(--line);
}

.total {
  display: flex;
  justify-content: space-between;
  font-size: 1.1rem;
}

/* The order number, which the counter calls out across the
   servery, is larger than the name beside it. */
.order .number {
  font-size: 1.5rem;
  font-weight: 700;
  margin-right: 0.25rem;
}

.order .top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

.order .actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  text-align: left;
  padding: 0.5rem;
  border-bottom: 1px solid var(--line);
}

h3 {
  font-size: 1rem;
  margin: 0 0 0.5rem;
}

/* The owner's menu cards: the fields sit side by side and
   wrap on to a second line on a narrow phone. */
.fields {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.75rem 1rem;
}

.fields label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0;
}

.fields label.check {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
  min-height: 2.75rem;
}

.fields input[type="number"] {
  width: 6rem;
}

.scroll {
  overflow-x: auto;
}

/* On a phone: the cart's summary, fixed to the foot of the
   screen, taking the student down to the order form. */
.cart-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.9rem 1rem;
  background: var(--brand-dark);
  color: #fff;
  font-weight: 700;
  text-align: center;
}

.page.with-side {
  padding-bottom: 4.5rem;
}

/* A wide screen: the order (or the kitchen list) sits in a
   second column beside the main one, and stays in view. */
@media (min-width: 48rem) {
  .page.with-side {
    display: grid;
    grid-template-columns: 1fr 20rem;
    gap: 1.5rem;
    align-items: start;
  }

  .side-column {
    position: sticky;
    top: 1rem;
  }

  .cart-bar {
    display: none;
  }
}
munotes.in262

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

Read it in its sections:

munotes.in263

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

  • The colours are variables in :root, named for their job, --ink, --muted, --error, not for their hue. A colour changes in one place, and the contrast of every text colour against its background was measured at 4.5 to 1 or more (NFR-8); the weakest, the error text, is 6.47 to 1.
  • box-sizing: border-box makes a width include the padding and the border, so a field set to width: 100% never spills out of its card.
  • The system font stack uses the font each device already has: nothing is downloaded, and text appears at once on a slow connection.
  • Sizes are in rem, relative to the user's own text size, so a student who has made the phone's text larger gets a larger page, not a broken one.
  • Forms are one field under another, every field the full width of its card: the easiest layout to use with one thumb. The stepper's plus and minus buttons are 2.75rem square, 44 CSS pixels at the normal text size, well above NFR-8's 24.
  • :focus-visible draws a thick blue outline around whatever the keyboard has reached, and only when the keyboard is in use, so every action can be followed without a mouse (NFR-8).
  • The status badges' class names are the statuses themselves, placed, preparing, ready, no_show, so the script sets the class straight from the data (Chapter 41). Each badge also says its status in words: colour never carries a meaning alone.
  • The one media query, min-width: 48rem, is the only rule that knows about wide screens. Below it, the order form sits under the menu and a bar fixed to the foot of the screen takes the student to it; above it, the form moves into a second column beside the menu and stays in view, and the bar disappears.

Talking to the server

Every request a page makes goes through one function. The whole module:

// Every request a page makes to the server goes through
// api(). It sends and reads JSON, keeps the sign-in cookie,
// and turns any failure into an ApiError the page can show.
//
// Open any page with ?mock=1 on the end of its address and
// the requests are answered by mock.js instead, with made-up
// data. That is how the pages were built and tried before
// the server existed.

import { mockFetch } from './mock.js';

const useMock = new URLSearchParams(location.search).has('mock');

export class ApiError extends Error {
  constructor(status, body) {
    const error = body && body.error ? body.error : {};
    super(error.message || `The server answered ${status}.`);
    this.status = status;
    this.code = error.code;
    this.details = error.details || {};
  }
}

export async function api(method, path, body) {
  const options = { method, headers: {} };
  if (method !== 'GET') {
    options.headers['Content-Type'] = 'application/json';
    options.body = JSON.stringify(body ?? {});
  }
  let res;
  try {
    res = useMock
      ? await mockFetch(method, path, body)
      : await fetch(path, options);
  } catch {
    throw new ApiError(0, { error: {
      message: 'Cannot reach the canteen server. Check your '
        + 'internet connection and try again.',
    } });
  }
  if (res.status === 204) return null;
  const data = await res.json().catch(() => null);
  if (!res.ok) throw new ApiError(res.status, data);
  return data;
}
munotes.in264

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

Forty-five lines carry five decisions:

  • fetch is the browser's own way to make a request; no library is needed. A page calls, for example, await api('POST', '/api/orders', { slot, items }) and receives the server's JSON as an object.
  • The sign-in cookie travels by itself. The pages and the API come from the same server, and fetch sends that server's cookies with every same-site request. No script ever sees the cookie, which is HttpOnly (Chapter 46).
  • Every request that changes something is sent as JSON, with its Content-Type saying so. The server refuses any change that is not JSON (Chapter 31, S7), which an ordinary form on another website cannot send.
  • Every refusal becomes one kind of error. ApiError carries the status, the error's code for the program, its message for people, and its details, the problem with each field. One shape of error from the server (Chapter 30) means one way of showing it on every page (Chapter 41).
  • A failure to reach the server at all is also an ApiError, with status 0 and a message in plain words, because on a phone at 12:15 the most common failure is the Wi-Fi, not the server.

A successful answer with nothing in it, 204, returns null; anything else is read as JSON. res.json().catch(() => null) keeps a broken or empty body from crashing the page: the status still says what happened.

The mock

The pages were started on 11 September, the same day as the server. For the student's pages, the team did not wait: api.js switches to a pretend server when the page's address ends in ?mock=1.

munotes.in265

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

// A pretend server, for building the pages before the real
// one existed (see api.js). It answers the same addresses
// with the same shapes of JSON the API specification sets
// out, from data kept in memory, so a page that works here
// works against the real server too.

const user = { id: 3, name: 'Priya Menon',
  email: 'priya@college.example', role: 'student' };

const items = [
  { id: 1, name: 'Veg Thali', category: 'meals', pricePaise: 7000,
    isVeg: true, isAvailable: true, stockLeft: 60 },
  { id: 3, name: 'Chicken Biryani', category: 'meals',
    pricePaise: 11000, isVeg: false, isAvailable: true,
    stockLeft: 4 },
  { id: 6, name: 'Vada Pav', category: 'snacks', pricePaise: 2000,
    isVeg: true, isAvailable: true, stockLeft: 0 },
  { id: 11, name: 'Cutting Chai', category: 'drinks',
    pricePaise: 1200, isVeg: true, isAvailable: true,
    stockLeft: 150 },
];

const slots = ['12:30', '12:40', '12:50', '13:00'].map(
  (time, i) => ({ time, cutoff: `12:${15 + i * 10}`, open: true }));

const orders = [];

function reply(status, data) {
  return new Response(data === undefined ? null
    : JSON.stringify(data), {
    status, headers: { 'Content-Type': 'application/json' },
  });
}

export async function mockFetch(method, path, body) {
  const route = `${method} ${path.split('?')[0]}`;
  if (route === 'GET /api/auth/me') return reply(200, { user });
  if (route === 'POST /api/auth/logout') return reply(204);
  if (route === 'GET /api/menu') return reply(200, { items });
  if (route === 'GET /api/slots') return reply(200, { slots });
  if (route === 'GET /api/orders/mine') {
    return reply(200, { orders });
  }
  if (route === 'POST /api/orders') {
    const lines = body.items.map((line) => {
      const item = items.find((i) => i.id === line.menuItemId);
      return { menuItemId: item.id, name: item.name,
        quantity: line.quantity, unitPricePaise: item.pricePaise };
    });
    const order = {
      id: 100 + orders.length, slot: body.slot, status: 'placed',
      items: lines, totalPaise: lines.reduce(
        (sum, l) => sum + l.quantity * l.unitPricePaise, 0),
    };
    orders.push(order);
    return reply(201, { order });
  }
  return reply(404, { error: { code: 'not_found',
    message: `The mock does not answer ${route}.` } });
}

The mock answers six requests, who is signed in, signing out, the menu, the slots, my orders and placing an order: everything the menu page and the orders page need to be drawn and to place an order. It answers with the shapes of JSON the API specification of Chapter 30 promises, and data chosen to show every case a page must handle: an item with plenty left, one with only 4 left, one sold out. mockFetch returns a real Response object, the same kind of thing fetch returns, which is why api.js can use either without knowing which.

Its limits, and why they are acceptable. It never refuses an order and cannot cancel one, so the pages' handling of sold_out and slot_closed, and cancelling, were tried against the real server. It signs nobody in or up: its user, Priya, is always signed in already. And it knows only that one user, a student, so the counter's and the owner's pages were built against the real server once its routes answered. A mock is a tool for starting early, not a substitute for the real thing, and the book's tests all run against the real server (Chapters 50 to 55).

munotes.in266

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

Checking that the server hands out the pages

The browser is the real test of a page: open it, look at it at 320 pixels wide in the developer tools' device view, and watch the console for errors. What can be checked from a terminal is that every file is served, with the type the browser needs:

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ for p in / /menu.html /orders.html /counter.html /owner.html \
>     /css/app.css /js/api.js /favicon.svg /nowhere; do \
>   curl -s -o /dev/null -w "%{http_code} %{content_type} $p\n" \
>     localhost:3000$p; done
200 text/html; charset=utf-8 /
200 text/html; charset=utf-8 /menu.html
200 text/html; charset=utf-8 /orders.html
200 text/html; charset=utf-8 /counter.html
200 text/html; charset=utf-8 /owner.html
200 text/css; charset=utf-8 /css/app.css
200 text/javascript; charset=utf-8 /js/api.js
200 image/svg+xml /favicon.svg
404 text/html; charset=utf-8 /nowhere

The address / is answered with index.html, the sign-in page. Each file comes with its type, text/html, text/css, text/javascript or image/svg+xml, which is what lets the browser treat it as a page, a stylesheet, a script or a picture. And an address that does not exist answers 404 with the HTML page above, not an error in JSON: JSON is for the API's own addresses (Chapter 48).

Do this for your project

  1. Give every page the same skeleton: the viewport tag, the stylesheet, one module script, a header, one <h1>, a message area.
  2. Tie every field to a visible label, and give every field the autocomplete value that says what it holds.
  3. Repeat the server's rules in required, minlength and maxlength for the user's sake, and never rely on them.
  4. Write the stylesheet for the narrowest phone first, in rem, with colours as named variables, and add wide-screen rules in one media query.
  5. Put every request through one module that speaks JSON and turns every failure, including no connection at all, into one kind of error.
  6. If the server is not ready, build against a mock that follows the API specification exactly, and say what it does not cover.

Mistakes that cost marks

No viewport tag, and a page that shrinks to a postage stamp on a phone.

munotes.in267

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

Placeholders instead of labels: grey text that vanishes as the user types, and that a screen reader may never read.

fetch calls scattered through every page, each handling errors its own way, or not at all.

Colours typed in fifty places, so a change of brand colour is fifty edits and a missed one.

A desktop design squeezed onto a phone instead of a phone design given more room.

The mock left switched on, so the demonstration shows made-up data. Here it takes ?mock=1 in the address, and nothing else turns it on.

Quick revision

  • Pages: viewport tag, one module script, labels tied to fields, autocomplete, a role="alert" message area, aria-current, aria-live.
  • Browser checks (required, minlength) are for the user; the server decides.
  • Stylesheet: phone first, one media query at 48rem, rem units, colours as variables, :focus-visible, colour never alone.
  • api.js: one door to the server; JSON both ways; the cookie travels by itself; every failure an ApiError.
  • The mock: the same shapes as the API, for starting early; limited to the student's side.

Questions you must be able to answer

1. Why does every page carry <meta name="viewport" content="width=device-width, initial-scale=1">? Because without it a phone lays the page out as if it were a desktop screen and shrinks it to fit, so text is tiny and every tap misses. The tag tells the phone to use its own width, which is what a stylesheet written for phones expects.

2. The browser already checks the form. Why does the server check again? Because the browser's checks can be switched off or bypassed by anyone who sends a request without the page, so they only help honest users catch mistakes early. The server's checks are the ones that protect the data, and it makes them on every request.

3. What does "mobile first" mean in the stylesheet? The rules outside any media query are the layout for a narrow phone, and a media query adds rules only for wider screens. A design made for the phone and given more room works everywhere, while a design made for a desktop and squeezed breaks on phones.

4. Why does every request go through one function, api()? So that JSON is sent and read in one way, every failure, including a failure to reach the server at all, becomes one kind of error with one way of being shown, and a change to how requests work is made in one place.

5. How does the sign-in cookie reach the server if no script handles it? The browser sends a site's cookies with every request to that site by itself, and the pages and the API are served from the same site. The cookie is HttpOnly, so scripts cannot read it at all, which is exactly what protects it from injected scripts.

munotes.in268

Frontend Implementation, Part 1: the Pages, the Stylesheet and Talking to the Server

6. What was the mock for, and what could it not do? It let the student's menu and orders pages be built and tried before the server existed, by answering the same requests with the same shapes of JSON the API specification promises. It could not sign anyone in, refuse or cancel an order, or act as the counter or the owner, so those behaviours and pages were tried against the real server.

Contents This chapter on its own page

munotes.in269

Chapter Forty-One

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

Syllabus topic Module 2, "Application Development: Frontend implementation", second part: the page scripts.

In one line

Each page's script does the same four things: finds out who is signed in and sends anyone else away; asks the server for the data the page shows; draws the page from that data; and turns every tap into a request, then draws the page again from the server's answer, showing any refusal in plain words.

In the wording to use when asked: the client-side scripts implement each screen's behaviour as small modules sharing a helper library: an authentication guard that redirects by role, data loading through the API layer, rendering from state with the DOM API (never by inserting HTML strings), event handlers that submit changes and re-render from the server's response, consistent error display including per-field validation messages, and periodic polling where a screen must reflect changes made by other users.

The shared helpers

Chapter 27 showed one of these helpers, el, and why it never puts text into a page as HTML. The whole module:

// Small helpers every page uses.

import { api, ApiError } from './api.js';

// Makes an element. Text is always set as text, never as
// HTML, so a menu item named "<script>..." is shown as
// those characters and never run (that would be XSS).
export function el(tag, attributes = {}, ...children) {
  const node = document.createElement(tag);
  for (const [name, value] of Object.entries(attributes)) {
    if (name === 'class') node.className = value;
    else if (name.startsWith('on')) {
      node.addEventListener(name.slice(2), value);
    } else if (value === true) node.setAttribute(name, '');
    else if (value !== false && value != null) {
      node.setAttribute(name, value);
    }
  }
  // `false` is skipped as well as null and undefined, so a
  // page can write `ready && el(...)` for an optional part.
  for (const child of children.flat()) {
    if (child == null || child === false) continue;
    node.append(child instanceof Node ? child : String(child));
  }
  return node;
}

const rupees = new Intl.NumberFormat('en-IN',
  { style: 'currency', currency: 'INR' });

export function money(paise) {
  return rupees.format(paise / 100);
}

export const STATUS_WORDS = {
  placed: 'Placed',
  preparing: 'Being prepared',
  ready: 'Ready to collect',
  collected: 'Collected',
  cancelled: 'Cancelled',
  no_show: 'Not collected',
};

export function statusBadge(status) {
  return el('span', { class: `badge ${status}` },
    STATUS_WORDS[status]);
}

export function showMessage(text, kind = 'error') {
  const box = document.getElementById('message');
  box.textContent = text;
  box.className = kind === 'ok' ? 'message ok' : 'message';
  box.hidden = false;
}

export function hideMessage() {
  document.getElementById('message').hidden = true;
}

// Puts the server's per-field messages beside the fields.
// Only a refusal of input, code invalid_input, carries them:
// the details of any other refusal, such as a sold-out item's
// id and how many are left, are for the program, not for the
// page. A message for a field this form does not have (the
// server says pricePaise where the owner typed rupees) goes
// into the main message instead, so no message is ever lost.
export function showErrors(form, err) {
  clearErrors(form);
  const fields = err.code === 'invalid_input' ? err.details : {};
  const unplaced = [];
  for (const [name, text] of Object.entries(fields)) {
    const field = form.elements[name];
    if (!field || !field.id) {
      unplaced.push(text);
      continue;
    }
    const note = el('p', { class: 'field-error',
      id: `${field.id}-error` }, text);
    field.setAttribute('aria-invalid', 'true');
    field.setAttribute('aria-describedby', note.id);
    field.after(note);
  }
  showMessage([err.message, ...unplaced].join(' '));
}

export function clearErrors(form) {
  form.querySelectorAll('.field-error').forEach((n) => n.remove());
  form.querySelectorAll('[aria-invalid]').forEach((field) => {
    field.removeAttribute('aria-invalid');
    field.removeAttribute('aria-describedby');
  });
  hideMessage();
}

// Disables a form's buttons while its request is on the way,
// so a double tap cannot place the same order twice.
export function busy(form, on) {
  form.querySelectorAll('button').forEach((b) => {
    b.disabled = on;
  });
}

export function homeFor(role) {
  return role === 'student' ? '/menu.html' : '/counter.html';
}

// Every page but the sign-in page starts here: it finds out
// who is signed in, sends anyone else to the right place,
// fills in the header, and wires up the sign-out button.
export async function signedIn(...roles) {
  let user;
  try {
    ({ user } = await api('GET', '/api/auth/me'));
  } catch (err) {
    if (err instanceof ApiError && err.status === 401) {
      location.href = '/';
      return new Promise(() => {}); // the page is going away
    }
    throw err;
  }
  if (!roles.includes(user.role)) {
    location.href = homeFor(user.role);
    return new Promise(() => {});
  }
  document.getElementById('who').textContent = user.name;
  document.getElementById('signout').addEventListener('click',
    async () => {
      await api('POST', '/api/auth/logout');
      location.href = '/';
    });
  return user;
}
munotes.in270

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

What each helper is for:

  • el(tag, attributes, ...children) builds every element on every page. A child that is a string is added as text, so nothing a user typed can become markup (Chapter 31, S9). An attribute set to true is added, false or null is left out, and an attribute named on... becomes an event listener; a child that is false is skipped, so a page can write ready && el(...) for a part that is sometimes there.
  • money(paise) is the one place paise become rupees (ADR-2): Intl.NumberFormat with the Indian English locale writes 22000 paise as ₹220.00, and a larger sum with the grouping Indian readers expect, ₹1,23,456.50.
  • STATUS_WORDS and statusBadge turn a status the database holds, no_show, into the words a person reads, "Not collected", in a badge whose class is the status itself (Chapter 40's stylesheet colours it).
  • showMessage and hideMessage fill the page's one message area; kind decides whether it looks like a success or an error.
  • showErrors(form, err) puts each of the server's messages beside the field it concerns, marks the field invalid for screen readers, and tells them where its message is. Only a refusal of input, code invalid_input, carries messages per field; every other refusal shows its message alone. A message for a field the form does not have goes into the main message, so none is lost.
  • busy(form, on) disables a form's buttons while its request is on its way, so an impatient double tap cannot place the same order twice.
  • signedIn(...roles) is how every page but the sign-in page begins. It asks the server who is signed in; if nobody is, it goes to the sign-in page, and if the wrong role is, to that role's own page. It returns a promise that never settles in both cases, so the rest of the page's script simply stops while the browser leaves. Otherwise it writes the name into the header, wires up Sign out, and returns the user.
munotes.in271

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

That last point is the whole of the pages' "security": a convenience that sends people to the right place. The server refuses the wrong role on every request whatever any page does (Chapter 46).

Signing in and registering

import { api } from './api.js';
import { showErrors, clearErrors, busy, homeFor } from './ui.js';

const signinForm = document.getElementById('signin-form');
const registerForm = document.getElementById('register-form');

// Already signed in? Then go straight to the right page.
api('GET', '/api/auth/me').then(
  ({ user }) => {
    location.href = homeFor(user.role);
  },
  () => {}, // not signed in: stay on this page
);

async function signIn(email, password) {
  const { user } = await api('POST', '/api/auth/login',
    { email, password });
  location.href = homeFor(user.role);
}

signinForm.addEventListener('submit', async (event) => {
  event.preventDefault();
  clearErrors(signinForm);
  busy(signinForm, true);
  const data = new FormData(signinForm);
  try {
    await signIn(data.get('email'), data.get('password'));
  } catch (err) {
    showErrors(signinForm, err);
    busy(signinForm, false);
  }
});

registerForm.addEventListener('submit', async (event) => {
  event.preventDefault();
  clearErrors(registerForm);
  busy(registerForm, true);
  const data = new FormData(registerForm);
  const details = {
    name: data.get('name'),
    email: data.get('email'),
    password: data.get('password'),
  };
  try {
    await api('POST', '/api/auth/register', details);
    await signIn(details.email, details.password);
  } catch (err) {
    showErrors(registerForm, err);
    busy(registerForm, false);
  }
});

The page first asks who is signed in, and if anyone is, goes straight to their page: a student who reopens the site should not sign in twice. A new student's registration is followed by an ordinary sign-in with the same details, so there is only one way into the application. FormData reads the form's fields by their name attributes, which is why every input in Chapter 40 has one.

The menu and the cart

The longest script, because the menu page does the most: it shows the menu, keeps the order being put together, and places it.

import { api } from './api.js';
import {
  el, money, signedIn, showMessage, showErrors, clearErrors, busy,
} from './ui.js';

const CATEGORIES = { meals: 'Meals', snacks: 'Snacks',
  drinks: 'Drinks', desserts: 'Desserts' };
const MAX_PER_ITEM = 5;
const MAX_ITEMS = 10;

const menuBox = document.getElementById('menu');
const form = document.getElementById('order-form');
const cartLines = document.getElementById('cart-lines');
const cartEmpty = document.getElementById('cart-empty');
const cartTotal = document.getElementById('cart-total');
const slotSelect = document.getElementById('slot');
const placeButton = document.getElementById('place');
const cartBar = document.getElementById('cart-bar');

let items = [];
const cart = new Map(); // menu item id -> quantity

const itemCount = () =>
  [...cart.values()].reduce((sum, q) => sum + q, 0);

function note(item) {
  if (!item.isAvailable) return 'Not on the menu today';
  if (item.stockLeft === 0) return 'Sold out';
  if (item.stockLeft < 10) return `Only ${item.stockLeft} left`;
  return '';
}

function change(item, by) {
  const quantity = (cart.get(item.id) || 0) + by;
  if (quantity > 0) cart.set(item.id, quantity);
  else cart.delete(item.id);
  render();
}

function row(item) {
  const quantity = cart.get(item.id) || 0;
  const orderable = item.isAvailable && item.stockLeft > 0;
  const most = Math.min(MAX_PER_ITEM, item.stockLeft);
  return el('div', { class: orderable ? 'item' : 'item off' },
    el('div', { class: 'about' },
      el('div', { class: 'name' }, item.name),
      el('div', {},
        el('span', { class: item.isVeg ? 'badge veg' : 'badge nonveg' },
          item.isVeg ? 'Veg' : 'Non-veg'),
        ' ', money(item.pricePaise)),
      note(item) && el('div', { class: 'note' }, note(item))),
    orderable && el('div', { class: 'stepper' },
      el('button', {
        type: 'button', class: 'quiet', disabled: quantity === 0,
        'aria-label': `One less ${item.name}`,
        onclick: () => change(item, -1),
      }, '-'),
      el('output', {}, quantity),
      el('button', {
        type: 'button',
        disabled: quantity >= most || itemCount() >= MAX_ITEMS,
        'aria-label': `One more ${item.name}`,
        onclick: () => change(item, 1),
      }, '+')));
}

function renderMenu() {
  const sections = Object.entries(CATEGORIES).map(([key, title]) => {
    const inIt = items.filter((item) => item.category === key);
    return inIt.length === 0 ? null
      : el('section', { class: 'category' },
        el('h2', {}, title), inIt.map(row));
  });
  menuBox.replaceChildren(...sections.filter(Boolean));
}

function renderCart() {
  const chosen = items.filter((item) => cart.has(item.id));
  cartLines.replaceChildren(...chosen.map((item) => {
    const q = cart.get(item.id);
    return el('li', {}, el('span', {}, `${q} x ${item.name}`),
      el('span', {}, money(q * item.pricePaise)));
  }));
  const total = chosen.reduce(
    (sum, item) => sum + cart.get(item.id) * item.pricePaise, 0);
  cartTotal.textContent = money(total);
  cartEmpty.hidden = chosen.length > 0;
  // On a phone the order form is below the whole menu, so a
  // bar at the foot of the screen shows the cart meanwhile.
  const n = itemCount();
  cartBar.hidden = n === 0;
  cartBar.textContent = `${n} ${n === 1 ? 'item' : 'items'}, `
    + `${money(total)}: review and order`;
  placeButton.disabled = chosen.length === 0
    || slotSelect.disabled;
}

function render() {
  renderMenu();
  renderCart();
}

async function loadMenu() {
  ({ items } = await api('GET', '/api/menu'));
  // Drop anything that has become unavailable or run short.
  for (const item of items) {
    const q = cart.get(item.id);
    if (q && (!item.isAvailable || item.stockLeft < q)) {
      cart.delete(item.id);
    }
  }
  render();
}

async function loadSlots() {
  const { slots } = await api('GET', '/api/slots');
  const open = slots.filter((slot) => slot.open);
  slotSelect.replaceChildren(...open.map((slot) =>
    el('option', { value: slot.time },
      `${slot.time} (order by ${slot.cutoff})`)));
  slotSelect.disabled = open.length === 0;
  if (open.length === 0) {
    slotSelect.append(el('option', {}, 'Ordering has closed today'));
  }
}

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  clearErrors(form);
  busy(form, true);
  const body = {
    slot: slotSelect.value,
    items: [...cart].map(([menuItemId, quantity]) =>
      ({ menuItemId, quantity })),
  };
  try {
    const { order } = await api('POST', '/api/orders', body);
    cart.clear();
    showMessage(`Order ${order.id} is placed for ${order.slot}. `
      + 'Give this number at the counter.', 'ok');
  } catch (err) {
    showErrors(form, err);
  }
  busy(form, false);
  await Promise.all([loadMenu(), loadSlots()]);
});

try {
  await signedIn('student');
  await Promise.all([loadMenu(), loadSlots()]);
} catch (err) {
  showMessage(err.message);
}
munotes.in272

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

Read it as data and drawing:

munotes.in273

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

  • The data is two things: items, exactly what the server last sent, and cart, a Map from item id to quantity, the one thing the page holds that the server does not.
  • The drawing is render(), which rebuilds the menu and the cart from that data every time anything changes. Nothing is ever changed on the page directly; the data is changed and the page redrawn. That makes the page impossible to get into a state the data does not describe, which is the idea behind every frontend framework, here in a hundred and fifty lines.
  • The limits are shown before they are refused. The plus button is disabled at 5 of an item, at the stock left, and at 10 items in the order: FR-7's rules, repeated so that a student cannot tap into a refusal. The server applies them again.
  • Each item says what is special about it in words: "Sold out", "Only 4 left" below 10 (FR-4), "Not on the menu today".
  • Placing an order sends the slot and the list of items and quantities, exactly the body the API specifies (Chapter 30). On success the page says the order's number and asks the student to give it at the counter (FR-11); on refusal it shows why. Either way it asks for the menu and the slots again, because the stock and the open slots have changed.
  • Reloading the menu drops from the cart anything that has meanwhile sold out or run short, rather than leaving the student to find out when they order.

The student's orders

import { api } from './api.js';
import {
  el, money, signedIn, showMessage, hideMessage, statusBadge,
} from './ui.js';

const list = document.getElementById('orders');

// Cancelling asks twice, on the page itself. (A pop-up from
// confirm() is not shown at all inside an Android WebView
// unless the app is written to show it, so it is not used.)
function cancelButton(order) {
  const ask = el('button', { type: 'button', class: 'quiet' },
    'Cancel this order');
  const box = el('div', { class: 'actions' }, ask);
  ask.addEventListener('click', () => {
    box.replaceChildren(
      el('button', { type: 'button', onclick: () => cancel(order) },
        'Yes, cancel it'),
      el('button', { type: 'button', class: 'quiet',
        onclick: () => box.replaceChildren(ask) }, 'Keep it'));
  });
  return box;
}

async function cancel(order) {
  try {
    await api('POST', `/api/orders/${order.id}/cancel`);
    showMessage(`Order ${order.id} is cancelled.`, 'ok');
  } catch (err) {
    showMessage(err.message);
  }
  await load();
}

function card(order) {
  return el('article', { class: 'card order' },
    el('div', { class: 'top' },
      el('h2', {}, `Order ${order.id}`), statusBadge(order.status)),
    el('p', {}, `Pickup at ${order.slot}`),
    el('ul', { class: 'lines' }, order.items.map((line) =>
      el('li', {}, el('span', {}, `${line.quantity} x ${line.name}`),
        el('span', {}, money(line.quantity * line.unitPricePaise))))),
    el('p', { class: 'total' }, el('span', {}, 'Total'),
      el('strong', {}, money(order.totalPaise))),
    order.status === 'placed' && cancelButton(order));
}

async function load() {
  try {
    const { orders } = await api('GET', '/api/orders/mine');
    list.replaceChildren(...(orders.length > 0 ? orders.map(card)
      : [el('p', {}, 'No orders today yet. ',
        el('a', { href: '/menu.html' }, 'See the menu'), '.')]));
  } catch (err) {
    showMessage(err.message);
  }
}

await signedIn('student');
hideMessage();
await load();
// Keep the statuses fresh, but not while the page is hidden.
setInterval(() => {
  if (!document.hidden) load();
}, 15000);
munotes.in274

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

Two details matter here. Cancelling asks twice on the page itself: the browser's confirm() box would be simpler, but inside the Android app's WebView it is not shown at all unless the app is written to show it (Chapter 59), and the student would never see the question. And the page asks the server again every 15 seconds, as FR-12 requires, but not while it is hidden: a phone in a pocket with the page open behind another app should not keep asking.

The counter

import { api } from './api.js';
import { el, money, signedIn, showMessage, statusBadge } from './ui.js';

// The next step or steps for an order in each status.
const NEXT = {
  placed: [['preparing', 'Start preparing']],
  preparing: [['ready', 'Mark ready']],
  ready: [['collected', 'Collected and paid'],
    ['no_show', 'Not collected']],
};

const list = document.getElementById('orders');
const slotSelect = document.getElementById('slot');
const kitchen = document.getElementById('kitchen');
const kitchenNote = document.getElementById('kitchen-slot');

async function move(order, status) {
  try {
    await api('PATCH', `/api/orders/${order.id}/status`, { status });
  } catch (err) {
    showMessage(err.message);
  }
  await load();
}

function card(order) {
  const steps = NEXT[order.status] || [];
  return el('article', { class: 'card order' },
    el('div', { class: 'top' },
      // The number is what the counter calls out and the
      // student shows, so it is the biggest thing on the card
      // (asked for at the acceptance session, 9 October).
      el('h2', {}, el('span', { class: 'number' }, order.id),
        ` ${order.studentName}`),
      statusBadge(order.status)),
    el('p', {}, `Pickup ${order.slot}, `
      + `total ${money(order.totalPaise)}`),
    el('ul', { class: 'lines' }, order.items.map((line) =>
      el('li', {}, `${line.quantity} x ${line.name}`))),
    steps.length > 0 && el('div', { class: 'actions' },
      steps.map(([status, words], i) => el('button', {
        type: 'button', class: i === 0 ? '' : 'quiet',
        onclick: () => move(order, status),
      }, words))));
}

async function loadKitchen(slot) {
  kitchen.hidden = !slot;
  if (!slot) {
    kitchenNote.textContent = 'Choose a slot to see what the '
      + 'kitchen still has to make for it.';
    return;
  }
  const { items } = await api('GET', `/api/kitchen?slot=${slot}`);
  kitchenNote.textContent = items.length > 0
    ? `For the ${slot} slot:` : `Nothing left to make for ${slot}.`;
  kitchen.tBodies[0].replaceChildren(...items.map((item) =>
    el('tr', {}, el('td', {}, item.name),
      el('td', {}, item.quantity))));
}

async function load() {
  const slot = slotSelect.value;
  const query = slot ? `?slot=${slot}` : '';
  try {
    const { orders } = await api('GET', `/api/orders${query}`);
    list.replaceChildren(...(orders.length > 0 ? orders.map(card)
      : [el('p', {}, 'No orders yet.')]));
    await loadKitchen(slot);
  } catch (err) {
    showMessage(err.message);
  }
}

const user = await signedIn('staff', 'owner');
document.getElementById('owner-link').hidden = user.role !== 'owner';
const { slots } = await api('GET', '/api/slots');
slotSelect.append(...slots.map((s) =>
  el('option', { value: s.time }, s.time)));
slotSelect.addEventListener('change', load);
await load();
setInterval(() => {
  if (!document.hidden) load();
}, 10000);
munotes.in275

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

NEXT is the state machine of Chapter 23 as the counter sees it: the next status or statuses for each status, with the button's words. The words are the triggers the state machine's arrows are labelled with, so the diagram, the buttons and the code all say "Start preparing", "Mark ready", "Collected and paid" and "Not collected". The first button of each order is the main one, and the second, for the choice at ready, is drawn quietly, so the common tap is the obvious one (NFR-7: every counter action a single tap).

The list refreshes itself every 10 seconds. That is the change Ganesh asked for at the first increment review, because during the rush he never had a free hand to reload the page (Chapter 15), recorded as SRS version 1.2's FR-14. Choosing a slot shows only that slot's orders and, beside them, the kitchen's list for it (FR-16).

The owner

import { api } from './api.js';
import {
  el, money, signedIn, showMessage, showErrors, clearErrors, busy,
  STATUS_WORDS,
} from './ui.js';

const menuList = document.getElementById('menu-list');
const report = document.getElementById('report');
const addItem = document.getElementById('add-item');
const addStaff = document.getElementById('add-staff');

// The owner types rupees; the server keeps whole paise.
const toPaise = (rupees) => Math.round(Number(rupees) * 100);

// One card per item, each field inside its own label, so the
// page works on a phone as well as on the counter's laptop.
function menuCard(item) {
  const price = el('input', { type: 'number', min: '1', max: '1000',
    step: '0.5', value: item.pricePaise / 100 });
  const on = el('input', { type: 'checkbox',
    checked: item.isAvailable });
  const stock = el('input', { type: 'number', min: '0', max: '1000',
    step: '1', value: item.stockLeft });
  const save = el('button', { type: 'button' }, 'Save');
  save.addEventListener('click', async () => {
    save.disabled = true;
    try {
      await api('PATCH', `/api/menu/${item.id}`, {
        pricePaise: toPaise(price.value), isAvailable: on.checked,
      });
      await api('PUT', `/api/menu/${item.id}/stock`,
        { stockLeft: Number(stock.value) });
      showMessage(`${item.name} is saved.`, 'ok');
    } catch (err) {
      showMessage(`${item.name}: ${err.message} `
        + Object.values(err.details).join(' '));
    }
    save.disabled = false;
  });
  return el('article', { class: 'card' },
    el('h3', {}, item.name),
    el('div', { class: 'fields' },
      el('label', {}, 'Price (₹)', price),
      el('label', {}, 'Stock left', stock),
      el('label', { class: 'check' }, on, ' On today'),
      save));
}

async function loadMenu() {
  const { items } = await api('GET', '/api/menu');
  menuList.replaceChildren(...items.map(menuCard));
}

async function loadReport() {
  const { report: day } = await api('GET', '/api/reports/daily');
  const counts = day.byStatus.map((row) =>
    `${STATUS_WORDS[row.status]}: ${row.orders}`).join(', ');
  const sold = day.items.length === 0
    ? el('p', {}, 'Nothing has been collected and paid for yet.')
    : el('div', { class: 'scroll' }, el('table', {},
      el('thead', {}, el('tr', {}, el('th', {}, 'Item sold'),
        el('th', {}, 'Quantity'), el('th', {}, 'Takings'))),
      el('tbody', {}, day.items.map((row) => el('tr', {},
        el('td', {}, row.name), el('td', {}, row.quantity),
        el('td', {}, money(row.revenuePaise)))))));
  report.replaceChildren(
    el('p', {}, `Orders on ${day.date}: ${counts || 'none yet'}.`),
    sold,
    el('p', { class: 'total' }, el('span', {}, 'Collected today'),
      el('strong', {}, money(day.revenuePaise))));
}

addItem.addEventListener('submit', async (event) => {
  event.preventDefault();
  clearErrors(addItem);
  busy(addItem, true);
  const data = new FormData(addItem);
  try {
    await api('POST', '/api/menu', {
      name: data.get('name'),
      category: data.get('category'),
      pricePaise: toPaise(data.get('price')),
      isVeg: data.get('isVeg') === 'on',
    });
    addItem.reset();
    showMessage('The item is added. Set its stock above.', 'ok');
    await loadMenu();
  } catch (err) {
    showErrors(addItem, err);
  }
  busy(addItem, false);
});

addStaff.addEventListener('submit', async (event) => {
  event.preventDefault();
  clearErrors(addStaff);
  busy(addStaff, true);
  const data = new FormData(addStaff);
  try {
    const { user } = await api('POST', '/api/users/staff', {
      name: data.get('name'), email: data.get('email'),
      password: data.get('password'),
    });
    addStaff.reset();
    showMessage(`${user.name} can now sign in at the counter.`, 'ok');
  } catch (err) {
    showErrors(addStaff, err);
  }
  busy(addStaff, false);
});

await signedIn('owner');
await Promise.all([loadMenu(), loadReport()]);
munotes.in276

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

The owner types rupees and sees rupees; toPaise turns what she typed into whole paise before anything is sent. It rounds, because a computer's fractions are not exact: 70.1 times 100 comes out as 7009.999999999999, and rounding makes it exactly 7010. Each menu item is a card with its price, its stock and whether it is on today's menu, and one Save button that sends two requests: the price and availability as a PATCH, then the stock as a PUT, because the API gives the stock its own address (Chapter 30). The report shows the day's orders by status and the takings of every item collected and paid for (FR-17).

munotes.in277

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

What the pages receive

The scripts only ever see JSON. The same requests the menu page makes, made from a terminal, show exactly what menu.js receives, and the shapes the mock of Chapter 40 imitates. Sign in as Priya, keeping her cookie in a file as the browser would, then read the menu and the slots and place an order:

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s -c jar -H 'Content-Type: application/json' \
>   -d '{"email":"priya@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login | jq -c .user
{"id":3,"name":"Priya Menon","email":"priya@college.example","role":"student"}
$ curl -s -b jar localhost:3000/api/menu \
>   | jq -c '.items[:3][] | {id, name, isAvailable, stockLeft}'
{"id":3,"name":"Chicken Biryani","isAvailable":true,"stockLeft":30}
{"id":5,"name":"Chole Bhature","isAvailable":false,"stockLeft":0}
{"id":4,"name":"Pav Bhaji","isAvailable":true,"stockLeft":50}
$ curl -s -b jar localhost:3000/api/slots | jq -c '.slots[]'
{"time":"12:30","cutoff":"12:15","open":true}
{"time":"12:40","cutoff":"12:25","open":true}
{"time":"12:50","cutoff":"12:35","open":true}
{"time":"13:00","cutoff":"12:45","open":true}
$ curl -s -b jar -H 'Content-Type: application/json' \
>   -d '{"slot":"12:40","items":[{"menuItemId":3,"quantity":2}]}' \
>   localhost:3000/api/orders | jq -c '.order | {id, slot, status, totalPaise}'
{"id":1,"slot":"12:40","status":"placed","totalPaise":22000}
$ curl -s -b jar localhost:3000/api/orders/mine | jq -c '.orders[].items'
[{"menuItemId":3,"name":"Chicken Biryani","quantity":2,"unitPricePaise":11000}]

The menu comes in categories and, within each, by name; the second item is on the menu but not today, which the page shows in words. The lab's clock stands at 10:30 on 29 September 2026, before every cut-off, so every slot is open. The order comes back with its number, its status and its total in paise, 2 Chicken Biryani at 11000 paise each; the page shows the total as ₹220.00. Each item of an order carries its name and the price agreed when it was placed, so a price changed later does not change this order (Chapter 29).

One pattern, five scripts

Every script follows the same pattern, and a new page for your own project should too:

  1. Guard: signedIn with the roles the page is for.
  2. Load: ask the API for what the page shows, through api().
  3. Draw: build the page from the data with el, never from HTML strings.
  4. Act: on a tap, disable the form, send the request, and show the answer or the refusal.
  5. Reload: draw the page again from what the server now says, and on the pages that show other people's changes, ask again on a timer.

Do this for your project

  1. Write one small module of helpers, and build every element through it, with text as text.
  2. Start every page with one guard that sends the wrong person away, and remember that it is a convenience: the server decides.
  3. Keep each page's data in variables, and redraw the page from them whenever they change.
  4. Show a limit before it is refused, and say what is special about an item in words.
  5. Disable a form while its request is on the way.
  6. Show every refusal in plain words, beside the field where there is one.
  7. Refresh only the pages that show other people's changes, and not while they are hidden.
munotes.in278

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

Mistakes that cost marks

innerHTML with data in it, and a menu item named <img src=x onerror=...> that runs in every student's browser.

Changing the page directly in twenty places, until the cart on the screen and the cart in the code disagree.

A double tap that places two orders, because the button stayed live while the first request was on its way.

A refusal shown as "Error 409", or not at all, or with a stray number on the end.

confirm() for a question that matters, in an app whose WebView never shows it.

A page that refreshes itself every second, all day, in every pocket.

Quick revision

  • el builds every element; text is always text (no innerHTML with data).
  • signedIn(roles) guards each page: a convenience; the server enforces.
  • Data, then drawing: keep items and cart, redraw from them.
  • Show limits before refusing; words for sold out, only N left.
  • busy stops a double tap; showErrors puts field messages beside fields, only for invalid_input.
  • Polling: my orders every 15 s (FR-12), the counter every 10 s (FR-14), not while hidden.

Questions you must be able to answer

1. Why does every page build its elements with a helper instead of writing HTML strings? Because a string of HTML with data inside it lets the data become markup: a menu item named with a script tag would run in every student's browser, which is cross-site scripting. The helper adds every piece of data as text, so it is always shown and never run.

2. What does "redraw the page from the data" mean, and why do it? The page's state is kept in variables, such as the items and the cart, and every change updates those variables and then rebuilds the page from them. The page then always shows exactly what the data says, and cannot drift into a state that no code describes.

3. The menu page disables the plus button at 5 of an item. Does that enforce FR-7? No. It helps the student by showing the limit before a request is refused, but anyone can send a request without the page. The server checks the quantity and the order size on every request, and that is what enforces the rule.

4. Why does the orders page ask for the orders every 15 seconds, and not only when the student taps something? Because the order's status is changed by someone else, the counter, and the student's page cannot know when. Asking again at an interval, polling, keeps it current; FR-12 sets the interval, and the page stops asking while it is hidden.

munotes.in279

Frontend Implementation, Part 2: the Menu, the Cart, the Orders and the Counter

5. How does the counter page keep its buttons in step with the rules? Its table of next steps lists, for each status, the moves the state machine allows from it, with the button words the state machine's arrows are labelled with. The server still refuses any other move, so a mistake in the page could not move an order wrongly.

6. Why does a form's button stay disabled while its request is on its way? So that a second tap, from impatience or a slow network, cannot send the same request twice, which for placing an order would mean two orders.

Contents This chapter on its own page

munotes.in280

Chapter Forty-Two

Backend Implementation, Part 1: the Express Application and Its Routes

Syllabus topic Module 2, "Application Development: Backend implementation", first part: the application, the middleware pipeline and the routes.

In one line

The backend is one Express application built by one function from its parts: every request passes a chain of middleware that logs it, sets the security headers, reads its JSON body and finds out who is signed in, then reaches a route that checks the caller's role, validates the input, calls a service, and turns the answer into JSON, while anything that goes wrong lands in one error handler at the end.

In the wording to use when asked: the backend is implemented as an Express application composed of a middleware pipeline for cross-cutting concerns, routers grouping endpoints by resource, route handlers that perform authorisation and validation before delegating to services, and terminal middleware for unmatched routes and for centralised error handling; the application is constructed by a factory that receives its dependencies, so that the same code runs in production and under test.

One file that builds the application

src/app.js is the whole backend's wiring, printed in Chapter 28: it takes the configuration, the database pool, the store, the clock and the log, builds the three services from them, and adds every piece of middleware and every router in order. Nothing in it opens a port. src/server.js does that, and it is the only file that starts anything:

'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { loadConfig } = require('./config');
const { createPool } = require('./db');
const { createStore } = require('./store');
const { createApp } = require('./app');
const { stampAt } = require('./rules');

// Settings from .env, if there is one. A variable already
// set in the real environment is left as it is.
const envFile = path.join(__dirname, '..', '.env');
if (fs.existsSync(envFile)) process.loadEnvFile(envFile);

const config = loadConfig(process.env);
const pool = createPool(config.db);
const store = createStore(pool);

// The real time, unless DEMO_TIME says otherwise.
const clock = config.demoTime
  ? () => new Date(config.demoTime)
  : () => new Date();
const app = createApp({ config, pool, store, clock });

const server = app.listen(config.port, config.host, () => {
  console.info('Canteen Pre-order is running at '
    + `http://${config.host}:${config.port}`);
  if (config.demoTime) {
    console.info('DEMO_TIME is set: the clock stands still at '
      + stampAt(config.demoTime, config.timeZone));
  }
});

// listen's callback is NEVER given an error: a port already in
// use arrives as an 'error' event on the server, and unhandled
// it kills the process with a stack trace nobody can read at a
// demonstration. Measured on Node.js 25 before this was written.
server.on('error', (err) => {
  console.error(err.code === 'EADDRINUSE'
    ? `Port ${config.port} is already in use. Stop whatever is `
      + 'using it, or set PORT in .env to another number.'
    : `The server could not start: ${err.message}`);
  process.exit(1);
});

// Expired sessions are cleared once an hour.
setInterval(() => {
  const now = stampAt(clock(), config.timeZone);
  store.users.deleteExpiredSessions(now).catch((err) =>
    console.error('Could not clear old sessions:', err.message));
}, 60 * 60 * 1000).unref();

// On Ctrl+C, or when systemd stops the service: stop taking
// new requests, let the ones in progress finish, close the
// database pool, and exit.
function shutDown(signal) {
  console.info(`${signal} received, shutting down.`);
  server.close(() => pool.end().then(() => process.exit(0)));
  setTimeout(() => process.exit(1), 10000).unref();
}
process.on('SIGINT', shutDown);
process.on('SIGTERM', shutDown);
munotes.in281

Backend Implementation, Part 1: the Express Application and Its Routes

Read what it does in order:

  • Settings first. It loads .env into the environment if the file is there, then loadConfig reads every setting from the environment and refuses to go on without a database password (Chapter 28).
  • The parts. The pool, the store, and the clock: the real time, unless DEMO_TIME is set, in which case the clock stands still at that moment and the server says so at start-up, so that nobody demonstrates with a frozen clock by accident (ADR-4).
  • Listening. app.listen(port, host): the host is 127.0.0.1 by default, so the application is not on the network at all until someone says otherwise (Chapter 57).
  • When it cannot listen. listen's callback is never handed an error, whatever its shape suggests: a port already in use arrives as an error event on the server, and with nothing listening for it the process ends in a stack trace. The handler turns it into one sentence that names the port and says what to do, which is the difference between a five-second fix and a bad minute in front of an examiner (Chapter 71).
  • Housekeeping. Expired sessions are deleted once an hour. unref() on the timer means this one interval does not by itself keep the program alive.
  • Stopping cleanly. On Ctrl+C or systemd's stop signal, the server stops taking new requests, lets the ones in progress finish, closes the database pool and exits; if that takes more than ten seconds it exits anyway. A server that is killed mid-request loses nothing, because every order is committed before its number is sent, but a clean stop is what lets a deployment restart the service without an error in anyone's browser (Chapter 60).

The pipeline, seen from a request

Chapter 28 listed the middleware in order. Here is what each one does to a request for POST /api/orders:

StepWhat it doesIf it refuses
requestLognotes the time, and writes one line when the answer has been sentnever refuses
securityHeaderssets the content security policy and the other headers on the answernever refuses
express.json({ limit: '10kb' })reads the body as JSON400 bad_json, or 413 too_large
jsonOnlya change must be JSON, and, if the browser says where it came from, from this site415 json_only, 403 wrong_origin
loadSessionturns the sid cookie into req.user, or nullnever refuses
the routersthe route itselfits own answers
apiNotFoundany other /api address404 not_found, as JSON
express.staticany other address: a file from public/nothing
pageNotFoundno file either404, the HTML page
errorHandlerevery error from every step abovethe error's own status, or 500
munotes.in282

Backend Implementation, Part 1: the Express Application and Its Routes

The order is the design. The log is first so that it times everything, including the failures. The security headers are set before any route can answer. JSON is read only for /api paths, so a request for a page is not parsed. And the error handler is last, because in Express an error passed on by any earlier step skips every ordinary step in between and arrives there.

Two of those steps are worth reading in full, because both are security controls rather than plumbing. This is the one that sets the headers, and the one that refuses a change that does not arrive as JSON from this site:

'use strict';

const { AppError } = require('../errors');

// Headers that tell the browser to refuse whole classes of
// attack: scripts from anywhere but this site, this site
// inside another site's frame, and guessing file types.
function securityHeaders(config) {
  const policy = [
    "default-src 'self'",
    "img-src 'self' data:",
    "object-src 'none'",
    "base-uri 'self'",
    "form-action 'self'",
    "frame-ancestors 'none'",
  ].join('; ');
  return (req, res, next) => {
    res.set('Content-Security-Policy', policy);
    res.set('X-Content-Type-Options', 'nosniff');
    res.set('Referrer-Policy', 'same-origin');
    res.set('X-Frame-Options', 'DENY');
    if (config.cookieSecure) {
      res.set('Strict-Transport-Security', 'max-age=15552000');
    }
    next();
  };
}

const CHANGES = ['POST', 'PUT', 'PATCH', 'DELETE'];

// A sandboxed frame sends the Origin "null", which is not an
// address at all, so a failure to read one is a refusal.
function sameHost(origin, host) {
  try {
    return new URL(origin).host === host;
  } catch {
    return false;
  }
}

// Every request that changes something must say it is JSON,
// and, if the browser names the page it came from, that page
// must be on this site. A form on another website can do
// neither, which is what stops it acting in a signed-in
// student's name (cross-site request forgery).
function jsonOnly(req, res, next) {
  if (!CHANGES.includes(req.method)) return next();
  if (!req.is('application/json')) {
    return next(new AppError(415, 'json_only',
      'Send the request body as JSON.'));
  }
  const origin = req.get('Origin');
  if (origin && !sameHost(origin, req.get('Host'))) {
    return next(new AppError(403, 'wrong_origin',
      'Requests must come from this site.'));
  }
  next();
}

module.exports = { securityHeaders, jsonOnly };

The content security policy is the line that matters most, and default-src 'self' is the whole of it: every script, style, font and frame may come only from this site, and because 'unsafe-inline' is not there, a string that somehow reached a page as markup cannot run as script either (Chapter 31). And jsonOnly refuses a POST that is not JSON, and one that says it came from somewhere else, which is what stops a form on another site from posting an order in a signed-in student's name.

munotes.in283

Backend Implementation, Part 1: the Express Application and Its Routes

The guards

Two small middlewares carry the whole of the access control the API promises (Chapter 30):

'use strict';

const { notSignedIn, forbidden } = require('../errors');

// Placed in front of a route: only signed-in users get past.
function requireUser(req, res, next) {
  next(req.user ? undefined : notSignedIn());
}

// Only signed-in users with one of these roles get past.
function requireRole(...roles) {
  return (req, res, next) => {
    if (!req.user) return next(notSignedIn());
    if (!roles.includes(req.user.role)) return next(forbidden());
    next();
  };
}

module.exports = { requireUser, requireRole };

requireUser lets any signed-in user past. requireRole('owner') builds a guard for the roles it is given: nobody signed in is a 401, the wrong role a 403. They are used in front of the routes, so that a route's own code never runs for a caller who may not use it. Deny by default means every route states its guard; the few public ones, the menu, the slots, signing in and registering, and the health check, are public because their routes say nothing, and each is a deliberate decision written down in the API table.

The routers

One router per area, each mounted under its own path in app.js. Orders are the fullest:

'use strict';

const express = require('express');
const validate = require('../validate');
const { requireUser, requireRole } = require('../middleware/guards');

function orderRoutes({ orders }) {
  const router = express.Router();
  const student = requireRole('student');
  const counter = requireRole('staff', 'owner');

  router.post('/', student, async (req, res) => {
    const order = await orders.place(req.user,
      validate.order(req.body));
    res.status(201).json({ order });
  });

  // Declared before '/:id', or "mine" would be read as an id.
  router.get('/mine', student, async (req, res) => {
    res.json({ orders: await orders.listMine(req.user) });
  });

  router.get('/', counter, async (req, res) => {
    const date = validate.dateQuery(req.query.date, orders.today());
    const slot = validate.slotQuery(req.query.slot);
    res.json({ date, orders: await orders.listForDay(date, slot) });
  });

  router.get('/:id', requireUser, async (req, res) => {
    const id = validate.idParam(req.params.id, 'Order');
    res.json({ order: await orders.get(req.user, id) });
  });

  router.post('/:id/cancel', student, async (req, res) => {
    const id = validate.idParam(req.params.id, 'Order');
    res.json({ order: await orders.cancel(req.user, id) });
  });

  router.patch('/:id/status', counter, async (req, res) => {
    const id = validate.idParam(req.params.id, 'Order');
    const { status } = validate.status(req.body);
    const order = await orders.changeStatus(req.user, id, status);
    res.json({ order });
  });

  return router;
}

module.exports = { orderRoutes };

Every handler is four lines or fewer, and each does the same four things in the same order: guard, validate, call a service, answer. Nothing else. No SQL, no rules, no decisions about the canteen: those belong to the layers below (Chapter 43).

munotes.in284

Backend Implementation, Part 1: the Express Application and Its Routes

Three details are worth copying:

  • /mine is declared before /:id. Express tries routes in the order they are added, so with the other order a request for /api/orders/mine would match /:id and the validator would be handed the word "mine".
  • The id in a path is validated too. validate.idParam refuses anything that is not a plain whole number in range and answers 404, so /api/orders/abc is "not found", not an error from the database (Chapter 47).
  • The status code is chosen deliberately: 201 for a new order, 200 for a change, and the resource itself in the body (Chapter 30).

The menu's routes are the same shape, with the owner's guard on everything but the public list:

'use strict';

const express = require('express');
const validate = require('../validate');
const { requireRole } = require('../middleware/guards');

function menuRoutes({ menu }) {
  const router = express.Router();
  const owner = requireRole('owner');

  // The menu is public: anyone may look before signing in.
  router.get('/', async (req, res) => {
    res.json({ items: await menu.list() });
  });

  router.post('/', owner, async (req, res) => {
    const item = await menu.create(validate.menuItem(req.body));
    res.status(201).json({ item });
  });

  router.patch('/:id', owner, async (req, res) => {
    const id = validate.idParam(req.params.id, 'Menu item');
    const changes = validate.menuItem(req.body, { partial: true });
    res.json({ item: await menu.update(id, changes) });
  });

  router.put('/:id/stock', owner, async (req, res) => {
    const id = validate.idParam(req.params.id, 'Menu item');
    const { stockLeft } = validate.stock(req.body);
    res.json({ item: await menu.setStock(id, stockLeft) });
  });

  return router;
}

module.exports = { menuRoutes };

PATCH takes any subset of an item's fields, which is why the validator is called with { partial: true }; PUT on /:id/stock sets one value, and sending it twice leaves the same number, which is why it is a PUT and not a POST.

The canteen's own day, the slots, the kitchen list and the report, has a router of its own, mounted at /api because its paths do not share one resource:

'use strict';

const express = require('express');
const validate = require('../validate');
const { requireRole } = require('../middleware/guards');

// The canteen's day: the pickup slots (public), what the
// kitchen still has to make for a slot (counter staff), and
// the day's sales report (the owner only).
function canteenRoutes({ orders }) {
  const router = express.Router();

  router.get('/slots', (req, res) => {
    res.json({ slots: orders.slots() });
  });

  router.get('/kitchen', requireRole('staff', 'owner'),
    async (req, res) => {
      const slot = validate.slotQuery(req.query.slot,
        { required: true });
      const date = orders.today();
      res.json({ date, slot, items: await orders.kitchen(date, slot) });
    });

  router.get('/reports/daily', requireRole('owner'),
    async (req, res) => {
      const date = validate.dateQuery(req.query.date, orders.today());
      res.json({ report: await orders.dailyReport(date) });
    });

  return router;
}

module.exports = { canteenRoutes };
munotes.in285

Backend Implementation, Part 1: the Express Application and Its Routes

Creating a staff account is the only thing in the users' router, because students register themselves:

'use strict';

const express = require('express');
const validate = require('../validate');
const { requireRole } = require('../middleware/guards');

// Only the owner creates counter staff accounts. Students
// register themselves, with their college email address.
function userRoutes({ auth }) {
  const router = express.Router();

  router.post('/staff', requireRole('owner'), async (req, res) => {
    const user = await auth.createStaff(validate.account(req.body));
    res.status(201).json({ user });
  });

  return router;
}

module.exports = { userRoutes };

And the health check is the one deliberate exception to the layer rule: it asks the pool directly, because whether the database answers is its entire question.

'use strict';

const express = require('express');

// Answers "is the application up, and can it reach its
// database?" A monitor, or a person, can call it at any time.
function healthRoutes({ pool }) {
  const router = express.Router();

  router.get('/', async (req, res) => {
    try {
      await pool.query('SELECT 1');
      res.json({ status: 'ok', database: 'ok' });
    } catch {
      res.status(503).json({ status: 'down', database: 'unreachable' });
    }
  });

  return router;
}

module.exports = { healthRoutes };

And the router that signs people in and out, which is the one a reader should compare with the service behind it (Chapter 46): every rule is in the service, and the router does nothing but read the request, call it, set or clear the cookie, and answer.

'use strict';

const express = require('express');
const validate = require('../validate');
const { requireUser } = require('../middleware/guards');

function cookieOptions(config) {
  return {
    httpOnly: true, // page scripts cannot read the cookie
    sameSite: 'lax', // not sent on another site's form posts
    secure: config.cookieSecure, // HTTPS only, once it is on
    maxAge: config.sessionHours * 60 * 60 * 1000,
    path: '/',
  };
}

function authRoutes({ auth, config }) {
  const router = express.Router();

  router.post('/register', async (req, res) => {
    const user = await auth.register(validate.account(req.body));
    res.status(201).json({ user });
  });

  router.post('/login', async (req, res) => {
    const { token, user } =
      await auth.login(validate.login(req.body), req.ip);
    res.cookie('sid', token, cookieOptions(config));
    res.json({ user });
  });

  router.post('/logout', async (req, res) => {
    await auth.logout(req.sessionToken);
    res.clearCookie('sid', cookieOptions(config));
    res.status(204).end();
  });

  router.get('/me', requireUser, (req, res) => {
    res.json({ user: req.user });
  });

  return router;
}

module.exports = { authRoutes };

Notice that the cookie is set in one place and cleared in one place, and that its options are the same in both: httpOnly, sameSite, and secure when the settings say the site is served over HTTPS. A cookie set with one set of options and cleared with another is a session that will not go away, which is a real bug and a common one.

munotes.in286

Backend Implementation, Part 1: the Express Application and Its Routes

Errors, and why the routes have no try

Not one route handler above catches anything. That is the point of the last middleware:

'use strict';

const path = require('node:path');
const { AppError } = require('../errors');

// An address under /api that no route answered.
function apiNotFound(req, res, next) {
  next(new AppError(404, 'not_found',
    'There is nothing at this address.'));
}

// Any other address that no file answered.
function pageNotFound(req, res) {
  res.status(404).sendFile(
    path.join(__dirname, '..', '..', 'public', '404.html'));
}

function send(res, status, code, message, details) {
  res.status(status).json({ error: { code, message, details } });
}

// The last stop for every error. An AppError was expected,
// so its message is shown. Anything else is a bug: the user
// gets a plain apology and the details go to the log, never
// to the browser, where they would help an attacker.
function errorHandler(log) {
  return (err, req, res, next) => {
    if (res.headersSent) return next(err);
    if (err instanceof AppError) {
      if (err.status === 429) {
        res.set('Retry-After', String(err.details.retryAfterSeconds));
      }
      return send(res, err.status, err.code, err.message,
        err.details);
    }
    if (err.type === 'entity.parse.failed') {
      return send(res, 400, 'bad_json',
        'The request body is not valid JSON.');
    }
    if (err.type === 'entity.too.large') {
      return send(res, 413, 'too_large',
        'The request body is too large.');
    }
    log.error(`${req.method} ${req.originalUrl} failed:`, err);
    send(res, 500, 'server_error',
      'Something went wrong on our side. Please try again.');
  };
}

module.exports = { apiNotFound, pageNotFound, errorHandler };
  • An AppError was expected: a wrong password, a sold-out item, an order that is not yours. Its status, code and message go to the browser as they are, and a 429 also carries a Retry-After header saying how many seconds to wait.
  • Two of Express's own errors are translated: a body that is not valid JSON becomes 400 bad_json, and one over the limit 413 too_large. Their own messages are not shown.
  • Anything else is a bug. It is logged in full, with the method and the address, and the browser is told only that something went wrong. An error's message or stack in a response is a gift to an attacker (Chapter 31, S11).
  • res.headersSent is checked first: if the answer has already begun, the only honest thing is to let Express finish it.

In Express 5, an async handler whose promise rejects is passed to the error handler automatically. That is why a route can await a service that throws conflict('sold_out', ...) and simply not think about it. The next section proves it rather than asserting it.

Seeing the pipeline work

Four requests show the chain refusing before any route runs, each at its own step:

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s -i localhost:3000/api/orders | head -1
HTTP/1.1 401 Unauthorized
$ curl -s -X POST -H 'Content-Type: text/plain' -d 'slot=12:40' \
>   localhost:3000/api/orders | jq -c .error
{"code":"json_only","message":"Send the request body as JSON."}
$ curl -s -X POST -H 'Content-Type: application/json' \
>   -H 'Origin: https://evil.example' -d '{}' \
>   localhost:3000/api/auth/logout | jq -c .error
{"code":"wrong_origin","message":"Requests must come from this site."}
$ curl -s localhost:3000/api/nothing | jq -c .error
{"code":"not_found","message":"There is nothing at this address."}
munotes.in287

Backend Implementation, Part 1: the Express Application and Its Routes

No session gives 401 before the route is reached; a body that is not JSON gives 415; a change from another site gives 403; and an unknown /api address gives 404 as JSON, not as the HTML page.

That leaves the claim about unexpected errors. Five lines, using the canteen's own error handler, show what Express 5 does with a promise that rejects inside an async handler. Save this in the project folder, beside src, and run it:

// Not part of the application. It shows what Express 5 does
// with an async handler that throws, using the canteen's own
// error handler: the browser is told nothing, the log gets
// everything.
const express = require('express');
const { errorHandler } = require('./src/middleware/errors');

const app = express();
app.get('/boom', async () => {
  throw new Error('a bug nobody expected');
});
app.use(errorHandler(console));
app.listen(3100, '127.0.0.1');
$ node boom.js > boom.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3100/boom
$ curl -s -i localhost:3100/boom | head -1
HTTP/1.1 500 Internal Server Error
$ curl -s localhost:3100/boom | jq -c .error
{"code":"server_error","message":"Something went wrong on our side. Please try again."}
$ grep -c 'a bug nobody expected' boom.log
3
$ head -1 boom.log
GET /boom failed: Error: a bug nobody expected
$ rm boom.js boom.log

The handler threw and the browser was told, in one plain sentence, that something went wrong: no message from the error, no file name, no stack. The log has the error in full, with the method and the address, three times because the address was asked for three times, which is the division Chapter 31 asks for (S11). Nothing in the route said a word about it.

Do this for your project

  1. Build your application in a function that takes its parts, and start it in a separate file.
  2. Put every cross-cutting concern in middleware, in a deliberate order, and write the order down.
  3. Guard every route by role, and make the public ones public on purpose.
  4. Keep handlers to guard, validate, call, answer: four lines, no SQL, no rules.
  5. Declare a fixed path such as /mine before a pattern such as /:id.
  6. Have one error handler, show expected errors, log the rest, and never send a stack to a browser.
  7. Stop cleanly on the signals your server manager sends.
munotes.in288

Backend Implementation, Part 1: the Express Application and Its Routes

Mistakes that cost marks

SQL in a route handler, so the same query is written three times and changed twice.

A try and catch around every handler, each answering differently, instead of one error handler.

/:id before /mine, so a page that worked yesterday breaks when a new route is added.

A stack trace in the browser on any failure.

A route with no guard that was meant to be for the owner, found by whoever tries the address.

The port, the password or the time zone written in the code, so the code must be edited to deploy it.

Quick revision

  • app.js builds, server.js starts; the same application runs under test.
  • Pipeline: log, security headers, JSON (10 kB), JSON-only and same site, session, routers, API 404, static files, page 404, error handler.
  • requireUser and requireRole: 401 with nobody signed in, 403 for the wrong role; deny by default.
  • Handler = guard, validate, call, answer; /mine before /:id; ids validated too.
  • One error handler: AppError shown, Express's parse errors translated, anything else logged and answered 500.
  • Express 5 sends a rejected promise from an async handler to the error handler.

Questions you must be able to answer

1. Why is the application built in one file and started in another? So that the same application can be built with different parts: the server builds it with the real database, the real clock and the real log, and the tests build it with a test database, a clock they can set and a quiet log. Nothing in the application opens a port, so a test never needs one.

2. What does the middleware pipeline do before any route runs? It starts timing the request and arranges for one line to be logged when it is answered, sets the security headers on the response, and, for /api paths only, reads the JSON body with a 10 kilobyte limit, refuses a change that is not JSON or comes from another site, and works out from the cookie who is signed in.

3. How does the API refuse the wrong user? Every route is preceded by a guard: requireUser for any signed-in user, or requireRole for named roles. With nobody signed in the guard answers 401, and with the wrong role 403, before the route's own code runs.

4. Why must /mine be declared before /:id? Because Express matches routes in the order they were added, so /:id would match /api/orders/mine first and the word "mine" would be treated as an order's id.

munotes.in289

Backend Implementation, Part 1: the Express Application and Its Routes

5. Why do the route handlers contain no error handling? Because Express 5 passes any error thrown in a handler, including a rejected promise from an async handler, to the error-handling middleware at the end of the pipeline, which answers expected errors with their own status and message and everything else with a logged 500.

6. What does the server do when it is asked to stop? It stops accepting new connections, lets the requests in progress finish, closes the database pool and exits, with a ten-second limit after which it exits anyway. That lets a service manager restart it without breaking anyone's request.

Contents This chapter on its own page

munotes.in290

Chapter Forty-Three

Backend Implementation, Part 2: the Services and the Business Rules

Syllabus topic Module 2, "Application Development: Backend implementation", second part: the services and the rules they apply.

In one line

Under the routes sit two layers: the rules, plain functions that know the canteen's decisions, when a slot closes, what an order costs, which status may follow which, and nothing else, and the services, which carry out a whole use case by combining those rules with the store, deciding what the answer is and what to refuse.

In the wording to use when asked: the service layer implements the application's use cases, coordinating domain rules, persistence and transactions, and raising domain-specific errors; the rules are pure functions of their arguments, with no dependence on the request, the clock or the database, which makes them deterministic and directly testable.

The rules: pure functions, and why

A rule here is a function whose answer depends only on what it is given. isSlotOpen('12:40', now, 'Asia/Kolkata') needs no database and reads no clock; it is told the time. The whole file:

'use strict';

// The canteen's rules as pure functions: no database, no
// network and no clock of their own. Each one is given what
// it needs, which is what makes every one of them testable.

const SLOTS = ['12:30', '12:40', '12:50', '13:00'];
const CUTOFF_MINUTES = 15;
const MAX_PER_ITEM = 5;
const MAX_ITEMS_PER_ORDER = 10;

const STATUSES = ['placed', 'preparing', 'ready',
  'collected', 'cancelled', 'no_show'];
const ACTIVE_STATUSES = ['placed', 'preparing', 'ready'];

// Which status may follow which, and whose move it is.
const MOVES = {
  placed: { preparing: 'counter', cancelled: 'student' },
  preparing: { ready: 'counter' },
  ready: { collected: 'counter', no_show: 'counter' },
};

// The parts of a moment on the canteen's own clock, whatever
// time zone the server itself happens to be set to.
function partsAt(now, timeZone) {
  const parts = new Intl.DateTimeFormat('en-CA', {
    timeZone, hourCycle: 'h23',
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
  }).formatToParts(now);
  const part = (type) => parts.find((p) => p.type === type).value;
  return {
    date: `${part('year')}-${part('month')}-${part('day')}`,
    time: `${part('hour')}:${part('minute')}:${part('second')}`,
    minutes: Number(part('hour')) * 60 + Number(part('minute')),
  };
}

// The date and the minute of the day, for the slot rules.
function localTime(now, timeZone) {
  const { date, minutes } = partsAt(now, timeZone);
  return { date, minutes };
}

// "2026-09-29 10:30:00": the form in which every time is
// written to the database. The application is the only
// clock; the database stores what it is given.
function stampAt(now, timeZone) {
  const { date, time } = partsAt(now, timeZone);
  return `${date} ${time}`;
}

function toMinutes(hhmm) {
  const [hours, minutes] = hhmm.split(':').map(Number);
  return hours * 60 + minutes;
}

function toHhmm(minutes) {
  const hh = String(Math.floor(minutes / 60)).padStart(2, '0');
  const mm = String(minutes % 60).padStart(2, '0');
  return `${hh}:${mm}`;
}

// Every slot with its cut-off, and whether it is still open.
function slotsAt(now, timeZone) {
  const { minutes } = localTime(now, timeZone);
  return SLOTS.map((time) => {
    const cutoff = toMinutes(time) - CUTOFF_MINUTES;
    return { time, cutoff: toHhmm(cutoff), open: minutes < cutoff };
  });
}

function isSlotOpen(slot, now, timeZone) {
  const found = slotsAt(now, timeZone).find((s) => s.time === slot);
  return Boolean(found && found.open);
}

// lines: [{ quantity, pricePaise }]. Integers, so exact.
function orderTotal(lines) {
  return lines.reduce(
    (sum, line) => sum + line.quantity * line.pricePaise, 0);
}

// Who may move an order from one status to another. The
// owner can do anything the counter staff can.
function canMove(from, to, role) {
  const mover = MOVES[from] && MOVES[from][to];
  if (!mover) return false;
  if (mover === 'student') return role === 'student';
  return role === 'staff' || role === 'owner';
}

// A cancelled order gives its food back to the stock. A
// no-show does not: that food was cooked and is gone.
function returnsStock(to) {
  return to === 'cancelled';
}

module.exports = {
  SLOTS, CUTOFF_MINUTES, MAX_PER_ITEM, MAX_ITEMS_PER_ORDER,
  STATUSES, ACTIVE_STATUSES, localTime, stampAt, toMinutes,
  toHhmm, slotsAt, isSlotOpen, orderTotal, canMove, returnsStock,
};
munotes.in291

Backend Implementation, Part 2: the Services and the Business Rules

Four things in it carry most of the canteen's behaviour:

  • The constants at the top are the single statement of the rules that have numbers in them: the four slots, the 15-minute cut-off, at most 5 of an item and 10 items in an order, the six statuses, and which three of them count as active. The validation, the pages and the tests all read them from here, so the day the canteen adds a slot one list changes.
  • MOVES is FR-15 and FR-13 as data: which status may follow which, and whose move it is. It is the state machine of Chapter 23, drawn there and printed here as the same five arrows.
  • The clock functions are ADR-4. partsAt asks Intl.DateTimeFormat what the date and time are in the canteen's time zone, whatever the server is set to, and everything else is built on it: localTime for the slot rules, stampAt for every time written to the database. Nothing here calls new Date(); the moment is always passed in.
  • orderTotal adds quantity times price in whole paise, so the sum is exact (ADR-2).

Pure functions are the easiest code in any project to test, because a test is one call and one comparison: no server, no database, no waiting. Chapter 51 tests every one of them, and the transcript below runs some of them in the lab.

The order service

The service is where a use case happens. Placing an order is the longest, and the one the whole system is built around:

'use strict';

const { withTransaction } = require('../db');
const { conflict, notFound } = require('../errors');
const {
  localTime, stampAt, slotsAt, isSlotOpen, orderTotal, canMove,
  returnsStock,
} = require('../rules');

// How each status reads in a sentence shown to a person.
const IN_WORDS = {
  placed: 'placed',
  preparing: 'being prepared',
  ready: 'ready',
  collected: 'collected',
  cancelled: 'cancelled',
  no_show: 'marked as not collected',
};

function orderService({ pool, store, config, clock }) {
  const today = () => localTime(clock(), config.timeZone).date;
  const stamp = () => stampAt(clock(), config.timeZone);

  // Why a conditional stock update took nothing: the item was
  // switched off, or there is not enough of it left.
  async function refusal(conn, menuItemId) {
    const item = await store.menu.findById(conn, menuItemId);
    if (!item || !item.isAvailable) {
      return conflict('item_unavailable',
        `${item ? item.name : 'That item'} is not available today.`,
        { menuItemId });
    }
    const left = item.stockLeft;
    return conflict('sold_out',
      left === 0 ? `${item.name} is sold out.`
        : `Only ${left} ${item.name} left.`,
      { menuItemId, stockLeft: left });
  }

  async function place(user, { slot, items }) {
    const now = clock();
    if (!isSlotOpen(slot, now, config.timeZone)) {
      throw conflict('slot_closed',
        `Ordering for the ${slot} slot has closed.`);
    }
    const date = localTime(now, config.timeZone).date;
    // Items are taken in id order, so two orders that share
    // items always lock them in the same order and cannot
    // deadlock waiting for each other.
    const wanted = [...items]
      .sort((a, b) => a.menuItemId - b.menuItemId);
    const id = await withTransaction(pool, async (conn) => {
      await store.orders.lockUser(conn, user.id);
      const active = await store.orders.countActive(
        conn, user.id, date, slot);
      if (active > 0) {
        throw conflict('slot_taken',
          `You already have an order for the ${slot} slot.`);
      }
      const lines = [];
      for (const { menuItemId, quantity } of wanted) {
        const taken = await store.menu.takeStock(
          conn, menuItemId, quantity);
        if (!taken) throw await refusal(conn, menuItemId);
        const item = await store.menu.findById(conn, menuItemId);
        lines.push(
          { menuItemId, quantity, pricePaise: item.pricePaise });
      }
      return store.orders.insert(conn, {
        userId: user.id, date, slot,
        totalPaise: orderTotal(lines), lines, now: stamp(),
      });
    });
    return store.orders.findById(id);
  }

  // A student sees only their own orders. Someone else's is
  // reported as not found, so that its existence is not
  // revealed either.
  async function get(user, id) {
    const order = await store.orders.findById(id);
    const mine = order && order.userId === user.id;
    if (!order || (user.role === 'student' && !mine)) {
      throw notFound('Order');
    }
    return order;
  }

  // Moves an order to a new status inside one transaction,
  // with the order's row locked while the move is decided.
  async function move(user, id, to, { ownOnly }) {
    await withTransaction(pool, async (conn) => {
      const order = await store.orders.lockById(conn, id);
      if (!order || (ownOnly && order.userId !== user.id)) {
        throw notFound('Order');
      }
      if (!canMove(order.status, to, user.role)) {
        throw conflict('invalid_move',
          `This order is ${IN_WORDS[order.status]}, so it cannot `
          + `be ${IN_WORDS[to]} now.`, { from: order.status, to });
      }
      await store.orders.setStatus(conn, id, to, stamp());
      if (returnsStock(to)) await store.menu.giveBackStock(conn, id);
    });
    return store.orders.findById(id);
  }

  async function dailyReport(date) {
    const byStatus = await store.orders.statusCounts(date);
    const items = await store.orders.itemSales(date);
    const revenuePaise = items.reduce(
      (sum, row) => sum + Number(row.revenuePaise), 0);
    return { date, byStatus, items, revenuePaise };
  }

  return {
    today,
    slots: () => slotsAt(clock(), config.timeZone),
    place,
    get,
    listMine: (user) => store.orders.listForUser(user.id, today()),
    listForDay: (date, slot) => store.orders.listForDay(date, slot),
    cancel: (user, id) =>
      move(user, id, 'cancelled', { ownOnly: true }),
    changeStatus: (user, id, to) =>
      move(user, id, to, { ownOnly: false }),
    kitchen: (date, slot) => store.orders.kitchenSummary(date, slot),
    dailyReport,
  };
}

module.exports = { orderService };
munotes.in292

Backend Implementation, Part 2: the Services and the Business Rules

Follow place from the top, and it is the sequence diagram of Chapter 22 in code:

munotes.in293

Backend Implementation, Part 2: the Services and the Business Rules

  1. Is the slot still open? A rule answers, using the service's own clock. If not, slot_closed, before the database is touched at all.
  2. Inside one transaction: lock the student's row, count their active orders for that slot, and refuse slot_taken if there is one (FR-10).
  3. Take the stock of each item, in the order of their ids, by the conditional update the store provides. A failure means the item is off the menu or short, and refusal looks up which, so the student is told item_unavailable or exactly how many are left.
  4. Insert the order and its lines, with the total the rules worked out and the time the application's own clock gives.
  5. Read the order back and return it, so the page gets the whole thing, its number included.

Anything thrown inside the transaction rolls it back: the stock taken for the first item cannot be left taken when the second is short (Chapter 45 proves it).

Three more things the service does, each in one place:

  • get hides other people's orders. A student asking for an order that is not theirs is told "not found", not "forbidden", so the answer does not even confirm that the order exists (NFR-5, S1).
  • move is one function for two use cases. Cancelling is a student moving an order to cancelled; the counter's buttons are a move to one of four others. Both lock the order's row, ask canMove whether this role may make this move, and, if the move returns stock, put it back. The message a refusal carries is built from IN_WORDS, so a student reads "This order is being prepared, so it cannot be cancelled now."
  • dailyReport asks the store for the counts and the sales and adds up the takings (FR-17).

Notice what the service never does: it never touches a request or a response, never writes SQL, and never reads the clock except through the one it was given. That is the layer rule of Chapter 28, and it is what lets the tests run the same service with a clock that stands still.

munotes.in294

Backend Implementation, Part 2: the Services and the Business Rules

The menu service

Short, because most of the work is the store's:

'use strict';

const { conflict, notFound } = require('../errors');

function menuService({ pool, store }) {
  // Turns MySQL's "duplicate entry" into a message a person
  // can act on. Any other error is passed on unchanged.
  async function uniqueName(work) {
    try {
      return await work();
    } catch (err) {
      if (err.code === 'ER_DUP_ENTRY') {
        throw conflict('name_taken',
          'A menu item with this name already exists.');
      }
      throw err;
    }
  }

  async function found(id) {
    const item = await store.menu.findById(pool, id);
    if (!item) throw notFound('Menu item');
    return item;
  }

  return {
    list: () => store.menu.list(),

    create: (item) => uniqueName(() => store.menu.create(item)),

    async update(id, changes) {
      await found(id);
      await uniqueName(() => store.menu.update(id, changes));
      return found(id);
    },

    async setStock(id, stockLeft) {
      await found(id);
      await store.menu.setStock(id, stockLeft);
      return found(id);
    },
  };
}

module.exports = { menuService };

It exists all the same, for two reasons: the routes keep one shape, guard, validate, call, answer, whatever the area; and the one decision that is not the store's, that changing an item that does not exist is a 404 rather than an empty answer, lives here rather than in a route.

The sign-in service

'use strict';

const crypto = require('node:crypto');
const { hashPassword, verifyPassword } = require('../passwords');
const { stampAt } = require('../rules');
const {
  AppError, badRequest, conflict, tooManyAttempts,
} = require('../errors');

// The cookie holds a random token; the database holds only
// its SHA-256 hash, so a leaked copy of the sessions table
// does not let anyone sign in.
function hashToken(token) {
  return crypto.createHash('sha256').update(token).digest('hex');
}

function publicUser(user) {
  return {
    id: user.id, name: user.name, email: user.email, role: user.role,
  };
}

function authService({ store, config, attempts, clock }) {
  const stamp = (moment) => stampAt(moment, config.timeZone);

  // Checked when the email is unknown, so a wrong email takes
  // as long as a wrong password, and the time a sign-in takes
  // does not reveal who has an account.
  const noSuchUser = hashPassword('there is no such user');

  async function create({ name, email, password }, role) {
    const passwordHash = await hashPassword(password);
    try {
      return await store.users.create(
        { name, email, passwordHash, role }, stamp(clock()));
    } catch (err) {
      if (err.code === 'ER_DUP_ENTRY') {
        throw conflict('email_taken',
          'An account with this email already exists.');
      }
      throw err;
    }
  }

  async function register(details) {
    const domain = details.email.split('@')[1];
    if (domain !== config.emailDomain) {
      throw badRequest('Some details need correcting.', {
        email: `Use your @${config.emailDomain} address.`,
      });
    }
    return create(details, 'student');
  }

  async function createStaff(details) {
    return create(details, 'staff');
  }

  async function login({ email, password }, ip) {
    const key = `${ip}|${email}`;
    const wait = attempts.blockedFor(key);
    if (wait > 0) throw tooManyAttempts(wait);
    const user = await store.users.findByEmail(email);
    const hash = user ? user.passwordHash : await noSuchUser;
    const ok = await verifyPassword(password, hash);
    if (!user || !ok || !user.isActive) {
      attempts.fail(key);
      throw new AppError(401, 'wrong_credentials',
        'The email or the password is wrong.');
    }
    attempts.reset(key);
    const token = crypto.randomBytes(32).toString('base64url');
    const now = clock();
    const expires = new Date(
      now.getTime() + config.sessionHours * 60 * 60 * 1000);
    await store.users.createSession(
      hashToken(token), user.id, stamp(now), stamp(expires));
    return { token, user: publicUser(user) };
  }

  async function logout(token) {
    if (token) await store.users.deleteSession(hashToken(token));
  }

  async function userFromToken(token) {
    if (!token) return null;
    return store.users.userForSession(
      hashToken(token), stamp(clock()));
  }

  return { register, createStaff, login, logout, userFromToken };
}

module.exports = { authService };
munotes.in295

Backend Implementation, Part 2: the Services and the Business Rules

Every line of it answers a control from the security design (Chapter 31):

  • The token and its hash (S6): 32 random bytes go to the browser as the cookie; only their SHA-256 is stored, so a stolen copy of the sessions table cannot sign anyone in.
  • The limit on failed sign-ins (S4, NFR-6): the key is the address the request came from and the email tried, so one address cannot lock out every account, and 5 failures in 15 minutes gives 429 with the seconds to wait.
  • One answer for every failed sign-in (S16): a wrong password, an unknown email and a switched-off account are all wrong_credentials. And when the email is unknown, a dummy hash is checked anyway, so that the time taken does not reveal who has an account.
  • The college domain (FR-1): registration refuses any other domain, with the message on the email field.
  • publicUser is what leaves the service: id, name, email and role. The password hash cannot reach a page by accident, because it is never in the object the service returns.

Running the rules

The rules can be tried in the terminal, because they are only functions. The lab's clock is frozen at 10:30 in the morning, so every slot is open; passing a different moment shows a cut-off working:

$ cd ~/canteen-preorder
$ node -e "
> const r = require('./src/rules');
> const tz = 'Asia/Kolkata';
> const at = (t) => new Date('2026-09-29T' + t + '+05:30');
> console.log(r.slotsAt(at('10:30:00'), tz));
> console.log('12:26, is 12:40 open?', r.isSlotOpen('12:40', at('12:26:00'), tz));
> console.log('12:24, is 12:40 open?', r.isSlotOpen('12:40', at('12:24:00'), tz));
> console.log('total', r.orderTotal([{ quantity: 2, pricePaise: 11000 }]));
> console.log('placed to ready by counter?', r.canMove('placed', 'ready', 'staff'));
> console.log('placed to cancelled by student?', r.canMove('placed', 'cancelled', 'student'));
> console.log('placed to cancelled by counter?', r.canMove('placed', 'cancelled', 'staff'));
> "
[
  { time: '12:30', cutoff: '12:15', open: true },
  { time: '12:40', cutoff: '12:25', open: true },
  { time: '12:50', cutoff: '12:35', open: true },
  { time: '13:00', cutoff: '12:45', open: true }
]
12:26, is 12:40 open? false
12:24, is 12:40 open? true
total 22000
placed to ready by counter? false
placed to cancelled by student? true
placed to cancelled by counter? false
munotes.in296

Backend Implementation, Part 2: the Services and the Business Rules

The 12:40 slot closes 15 minutes before it, at 12:25: open at 12:24, closed at 12:26. The counter cannot skip a step from placed to ready, and only the student may cancel. Every one of those answers is a requirement, and none of them needed a database or a wait.

The same clock rule can be seen from outside, through the API, by starting the server with its clock set to a moment late in the break:

$ cd ~/canteen-preorder
$ DEMO_TIME=2026-09-29T12:36:00+05:30 npm start > late.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s localhost:3000/api/slots | jq -c '.slots[]'
{"time":"12:30","cutoff":"12:15","open":false}
{"time":"12:40","cutoff":"12:25","open":false}
{"time":"12:50","cutoff":"12:35","open":false}
{"time":"13:00","cutoff":"12:45","open":true}
$ curl -s -c jar -H 'Content-Type: application/json' \
>   -d '{"email":"priya@college.example","password":"canteen-demo"}' \
>   -o /dev/null localhost:3000/api/auth/login
$ curl -s -b jar -H 'Content-Type: application/json' \
>   -d '{"slot":"12:30","items":[{"menuItemId":1,"quantity":1}]}' \
>   localhost:3000/api/orders | jq -c .error
{"code":"slot_closed","message":"Ordering for the 12:30 slot has closed."}

At 12:36 only the 13:00 slot is still open: 12:50's cut-off, 15 minutes before it, passed a minute ago. An order for a closed slot is refused with the reason, before anything is written. DEMO_TIME is the setting ADR-4 provides for exactly this: showing the system at a time of day that is not now.

Do this for your project

  1. Write your rules as functions of their arguments: no database, no clock, no request.
  2. Put every number and every list that is a decision in one place, and read it from there everywhere.
  3. Make the service do the use case: rules, store, transaction, and the refusals with their reasons.
  4. Never let a service see a request or a response.
  5. Pass the clock in, so that a test can set it and a demonstration can move it.
  6. Return only what the caller should see; never the password hash.
  7. Give every refusal a code for the program and a sentence for the person.

Mistakes that cost marks

Rules inside route handlers, so the same decision is made differently in two places.

new Date() deep in the code, so nothing can be tested at any other time of day and the server's own time zone decides the canteen's slots.

A service that answers res.json(...) and so can never be tested without a server.

A password hash in the object the API sends back, because the whole row was returned.

"Error" as the message for every refusal, with the reason known only to the code.

munotes.in297

Backend Implementation, Part 2: the Services and the Business Rules

The cut-off written as 15 in four files.

Quick revision

  • Rules: pure functions of their arguments; the constants, MOVES, the clock functions, orderTotal.
  • ADR-4: the application is the only clock; Intl.DateTimeFormat gives the canteen's time zone; DEMO_TIME moves it for a demonstration.
  • Services carry out use cases: the slot rule, the transaction, the stock, the insert, the refusals.
  • get answers 404 for someone else's order (S1); move serves cancelling and the counter alike.
  • Sign-in: 32 random bytes, only the SHA-256 stored (S6); the limiter keyed on address and email (S4); one message for every failure, with a dummy hash (S16); publicUser hides the hash.

Questions you must be able to answer

1. What makes the rules "pure", and why does it matter? Their answer depends only on their arguments, and they change nothing outside themselves: they read no clock, no database and no request. So a test calls one with values and compares the answer, with no server, no data and no waiting, and the same call always gives the same result.

2. Why is the clock passed into the services instead of read where it is needed? Because the canteen's rules depend on the time, and a test must be able to try 12:24 and 12:26 without waiting for them, and a demonstration must be able to run at a time when slots are open. One clock, given to the application, also keeps the slot decision and the stored times in step (ADR-4).

3. What does the order service do that the route cannot? It carries out the use case: asks the rules whether the slot is open, opens a transaction, locks the student's row, counts their orders for the slot, takes each item's stock, inserts the order and its lines, and raises the right refusal with its reason. The route only guards, validates, calls it and turns the answer into JSON.

4. Why does a student asking for someone else's order get 404 and not 403? Because 403 would confirm that the order exists and belongs to someone. "Not found" reveals nothing at all, which is what NFR-5 asks for.

5. How does the sign-in service avoid revealing which email addresses have accounts? Every failure gives the same message and the same code, whether the email is unknown, the password wrong or the account switched off; and when the email is unknown it still checks a password against a dummy hash, so the request takes the same time either way.

6. Where is the rule that a cancelled order returns its food to the stock, and a no-show does not? In the rules, as returnsStock, which is true only for cancelled. The service asks it after a move succeeds, so the decision is in one place and the state machine of Chapter 23 shows the same thing.

Contents This chapter on its own page

munotes.in298

Chapter Forty-Four

Database Integration, Part 1: the Connection Pool and Safe Queries

Syllabus topic Module 2, "Application Development: Database integration", first part: creating the database, the connection pool, and every query written safely.

In one line

Database integration is the layer where the application meets MySQL: a script that creates the database and the account the application uses, a pool that keeps a few connections open and lends one to each query, and a store in which every SQL statement in the whole application lives, each one sending its values separately as placeholders so that nothing a user types can change what a query does.

In the wording to use when asked: database integration comprises schema provisioning, connection management through a pool, and a data access layer that isolates all SQL from the rest of the application, using parameterised statements to prevent SQL injection, mapping between database column names and the application's own, and keeping the number of round trips to the database proportionate to the work.

Creating the database and its account

The schema of Chapter 29 says what the tables are. Two more things have to exist before it can be loaded: the databases themselves, and the account the application signs in to MySQL with. One short script, run once as the MySQL administrator:

-- Run ONCE, as the MySQL administrator (root), before
-- npm run db:setup. It makes the two databases and the one
-- account the application uses, allowed into those two only.
-- Change the password here AND in your .env file.

CREATE DATABASE IF NOT EXISTS canteen
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS canteen_test
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER IF NOT EXISTS 'canteen'@'localhost'
  IDENTIFIED BY 'change-this-password';
GRANT ALL PRIVILEGES ON canteen.* TO 'canteen'@'localhost';
GRANT ALL PRIVILEGES ON canteen_test.* TO 'canteen'@'localhost';
  • Two databases, the real one and the tests', so that running the tests can never touch the canteen's data.
  • utf8mb4 is the character set that can hold every character, including the Devanagari a student may write their name in, and emoji. MySQL's older utf8mb3 cannot store them at all.
  • An account of its own, canteen, allowed into those two databases and nothing else on the server, and only from the machine itself. The application never signs in as root.
  • The password is changed here and in .env together (Chapter 39). The one in the file is a placeholder, and a real server gets a different one.

Then one command builds the tables and fills them with the demonstration data:

'use strict';

// npm run db:setup
// Builds a fresh copy of the database: every table dropped
// and made again from sql/schema.sql, then filled from
// sql/seed.sql. All data already in it is lost.

const fs = require('node:fs');
const path = require('node:path');
const mysql = require('mysql2/promise');
const { loadConfig } = require('../src/config');

const SQL = path.join(__dirname, '..', 'sql');

async function resetDatabase(db) {
  const conn = await mysql.createConnection({
    host: db.host, port: db.port, database: db.database,
    user: db.user, password: db.password,
    multipleStatements: true, // a .sql file holds many
  });
  try {
    for (const file of ['schema.sql', 'seed.sql']) {
      await conn.query(fs.readFileSync(path.join(SQL, file), 'utf8'));
    }
  } finally {
    await conn.end();
  }
}

if (require.main === module) {
  const envFile = path.join(__dirname, '..', '.env');
  if (fs.existsSync(envFile)) process.loadEnvFile(envFile);
  const { db } = loadConfig(process.env);
  resetDatabase(db).then(
    () => console.info(`Database "${db.database}" is ready.`),
    (err) => {
      console.error('Setup failed:', err.message);
      process.exitCode = 1;
    });
}

module.exports = { resetDatabase };
munotes.in299

Database Integration, Part 1: the Connection Pool and Safe Queries

It runs the two SQL files in order, schema.sql then seed.sql, with multipleStatements on because a file holds many statements. That option is for this script only; the application's own pool does not have it, and that is deliberate: with it on, a value that ever reached the SQL text could carry a second statement with it.

The pool

'use strict';

const mysql = require('mysql2/promise');

// One pool for the whole application. A pool keeps a few
// connections open and lends one to each query, which is far
// quicker than opening a new connection every time.
function createPool(db) {
  return mysql.createPool({
    host: db.host,
    port: db.port,
    database: db.database,
    user: db.user,
    password: db.password,
    connectionLimit: 10,
    // DATE and DATETIME values arrive as text, exactly as
    // stored, instead of being converted to JavaScript Dates.
    dateStrings: true,
  });
}

// Runs work(conn) inside one transaction: every statement in
// it is kept, or, if anything throws, none of them is.
async function withTransaction(pool, work) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    const result = await work(conn);
    await conn.commit();
    return result;
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }
}

module.exports = { createPool, withTransaction };
  • A pool, not a connection. Opening a connection to MySQL costs a network round trip and an authentication, tens of milliseconds; a pool opens a few and lends one out for each query, which is why the load test of Chapter 63 answers the menu in milliseconds.
  • Ten connections is plenty for one canteen: each is used for the instant a query takes. More would consume MySQL's own limit for nothing.
  • dateStrings: true makes MySQL's DATE and DATETIME values arrive as text, exactly as stored, instead of being turned into JavaScript Date objects in the server's own time zone. That is ADR-4 again: the application decides what every time means, and the database stores what it is given (Chapter 43).
  • withTransaction is Chapter 45's subject. It is here because a transaction needs one connection for all its statements, which is exactly what a pool does not give you by default.

The store itself is assembled from three files, one for each kind of thing, and the file that joins them is this short:

munotes.in300

Database Integration, Part 1: the Connection Pool and Safe Queries

'use strict';

const { userStore } = require('./users');
const { menuStore } = require('./menu');
const { orderStore } = require('./orders');

// The one object the services use to reach the database.
function createStore(pool) {
  return {
    users: userStore(pool),
    menu: menuStore(pool),
    orders: orderStore(pool),
  };
}

module.exports = { createStore };

The pool is given to each part, and nothing else in the application ever sees it. That is what makes the store the only layer that speaks SQL: there is no other way to reach the database, because nobody else is handed the means (Chapter 28).

Every value is a placeholder

The store is the only part of the application that writes SQL. The users' store shows the shape of all of it:

'use strict';

// Every SQL statement about users and sessions. A value is
// always passed as a ? placeholder, never pasted into the SQL
// text, so nothing a user types can change what a query does.

function userStore(pool) {
  return {
    async findByEmail(email) {
      const [rows] = await pool.execute(
        `SELECT id, name, email, role,
                password_hash AS passwordHash,
                is_active AS isActive
           FROM users
          WHERE email = ?`, [email]);
      return rows[0] || null;
    },

    async create({ name, email, passwordHash, role }, now) {
      const [result] = await pool.execute(
        `INSERT INTO users
           (name, email, password_hash, role, created_at)
         VALUES (?, ?, ?, ?, ?)`,
        [name, email, passwordHash, role, now]);
      return { id: result.insertId, name, email, role };
    },

    async createSession(id, userId, now, expiresAt) {
      await pool.execute(
        `INSERT INTO sessions (id, user_id, created_at, expires_at)
         VALUES (?, ?, ?, ?)`, [id, userId, now, expiresAt]);
    },

    async userForSession(id, now) {
      const [rows] = await pool.execute(
        `SELECT u.id, u.name, u.email, u.role
           FROM sessions s
           JOIN users u ON u.id = s.user_id
          WHERE s.id = ? AND s.expires_at > ?
            AND u.is_active = TRUE`, [id, now]);
      return rows[0] || null;
    },

    async deleteSession(id) {
      await pool.execute('DELETE FROM sessions WHERE id = ?', [id]);
    },

    async deleteExpiredSessions(now) {
      const [result] = await pool.execute(
        'DELETE FROM sessions WHERE expires_at <= ?', [now]);
      return result.affectedRows;
    },
  };
}

module.exports = { userStore };

Every statement has ? where a value goes, and the values follow in an array. The value never becomes part of the SQL text. pool.execute prepares the statement, sends the values separately, and MySQL treats them as data whatever they contain. That is what makes SQL injection impossible here, and it is a habit, not a judgement: there is no "safe" value that may be pasted in instead.

What the alternative looks like, and why it is a hole:

// NEVER write this.
const [rows] = await pool.query(
  `SELECT * FROM users WHERE email = '${email}'`);
munotes.in301

Database Integration, Part 1: the Connection Pool and Safe Queries

With email set to ' OR '1'='1, the text sent to MySQL becomes SELECT * FROM users WHERE email = '' OR '1'='1', which matches every row. The same trick with a semicolon can add a statement of its own.

The store's other two files hold the same discipline. The menu's:

'use strict';

const COLUMNS = `id, name, category,
  price_paise AS pricePaise, is_veg AS isVeg,
  is_available AS isAvailable, stock_left AS stockLeft`;

// The columns a change may touch, and their names in SQL.
// Only these names can ever reach the SQL text.
const EDITABLE = {
  name: 'name',
  category: 'category',
  pricePaise: 'price_paise',
  isVeg: 'is_veg',
  isAvailable: 'is_available',
};

// MySQL keeps BOOLEAN as 1 or 0; the application wants true
// or false.
function toItem(row) {
  return {
    ...row,
    isVeg: Boolean(row.isVeg),
    isAvailable: Boolean(row.isAvailable),
  };
}

function menuStore(pool) {
  const store = {
    async list() {
      const [rows] = await pool.execute(
        `SELECT ${COLUMNS} FROM menu_items
          ORDER BY FIELD(category, 'meals', 'snacks',
                         'drinks', 'desserts'), name`);
      return rows.map(toItem);
    },

    async findById(db, id) {
      const [rows] = await db.execute(
        `SELECT ${COLUMNS} FROM menu_items WHERE id = ?`, [id]);
      return rows[0] ? toItem(rows[0]) : null;
    },

    async create(item) {
      const [result] = await pool.execute(
        `INSERT INTO menu_items
           (name, category, price_paise, is_veg, is_available)
         VALUES (?, ?, ?, ?, ?)`,
        [item.name, item.category, item.pricePaise, item.isVeg,
          item.isAvailable ?? true]);
      return store.findById(pool, result.insertId);
    },

    async update(id, changes) {
      const keys = Object.keys(changes);
      const sets = keys.map((k) => `${EDITABLE[k]} = ?`);
      const [result] = await pool.execute(
        `UPDATE menu_items SET ${sets.join(', ')} WHERE id = ?`,
        [...keys.map((k) => changes[k]), id]);
      return result.affectedRows === 1;
    },

    async setStock(id, stockLeft) {
      const [result] = await pool.execute(
        'UPDATE menu_items SET stock_left = ? WHERE id = ?',
        [stockLeft, id]);
      return result.affectedRows === 1;
    },

    // Takes `quantity` from the stock only if that many are
    // left, in ONE statement, so two orders for the last
    // plate cannot both succeed. True if it was taken.
    async takeStock(conn, id, quantity) {
      const [result] = await conn.execute(
        `UPDATE menu_items
            SET stock_left = stock_left - ?
          WHERE id = ? AND is_available = TRUE
            AND stock_left >= ?`,
        [quantity, id, quantity]);
      return result.affectedRows === 1;
    },

    // Puts a cancelled order's items back on the shelf.
    async giveBackStock(conn, orderId) {
      await conn.execute(
        `UPDATE menu_items m
           JOIN order_items oi ON oi.menu_item_id = m.id
            SET m.stock_left = m.stock_left + oi.quantity
          WHERE oi.order_id = ?`, [orderId]);
    },
  };
  return store;
}

module.exports = { menuStore };

Here one statement's text does vary, in update: the owner changes some fields and not others, so the list of column = ? pairs is built at run time. Notice where the column names come from: EDITABLE, a fixed table in the file. A name the request sent could never reach the SQL, because only the five keys of that table are ever looked up. Values are placeholders; identifiers come from the code.

munotes.in302

Database Integration, Part 1: the Connection Pool and Safe Queries

The orders' store is the largest, because an order is two tables:

'use strict';

const ORDER_COLUMNS = `o.id, o.user_id AS userId,
  u.name AS studentName, o.pickup_date AS pickupDate,
  TIME_FORMAT(o.pickup_slot, '%H:%i') AS slot, o.status,
  o.total_paise AS totalPaise, o.created_at AS createdAt,
  o.updated_at AS updatedAt`;

const FROM_ORDERS = 'FROM orders o JOIN users u ON u.id = o.user_id';

// Fetches the items of many orders in ONE query and hangs
// each item on its order. (One query per order would be the
// "N + 1 queries" mistake: 40 orders, 41 round trips.)
async function withItems(db, orders) {
  if (orders.length === 0) return [];
  const ids = orders.map((o) => o.id);
  const marks = ids.map(() => '?').join(', ');
  const [rows] = await db.execute(
    `SELECT oi.order_id AS orderId,
            oi.menu_item_id AS menuItemId, m.name,
            oi.quantity, oi.unit_price_paise AS unitPricePaise
       FROM order_items oi
       JOIN menu_items m ON m.id = oi.menu_item_id
      WHERE oi.order_id IN (${marks})
      ORDER BY oi.order_id, m.name`, ids);
  const byId = new Map(orders.map((o) => [o.id, { ...o, items: [] }]));
  for (const { orderId, ...item } of rows) {
    byId.get(orderId).items.push(item);
  }
  return [...byId.values()];
}

function orderStore(pool) {
  return {
    // Locks the student's own row until the transaction ends,
    // so two orders from one student are handled one by one.
    async lockUser(conn, userId) {
      await conn.execute(
        'SELECT id FROM users WHERE id = ? FOR UPDATE', [userId]);
    },

    async countActive(conn, userId, date, slot) {
      const [rows] = await conn.execute(
        `SELECT COUNT(*) AS n FROM orders
          WHERE user_id = ? AND pickup_date = ?
            AND pickup_slot = ?
            AND status IN ('placed', 'preparing', 'ready')`,
        [userId, date, slot]);
      return rows[0].n;
    },

    async insert(conn, order) {
      const { userId, date, slot, totalPaise, lines, now } = order;
      const [result] = await conn.execute(
        `INSERT INTO orders (user_id, pickup_date, pickup_slot,
           total_paise, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, ?)`,
        [userId, date, slot, totalPaise, now, now]);
      for (const line of lines) {
        await conn.execute(
          `INSERT INTO order_items (order_id, menu_item_id,
             quantity, unit_price_paise)
           VALUES (?, ?, ?, ?)`,
          [result.insertId, line.menuItemId, line.quantity,
            line.pricePaise]);
      }
      return result.insertId;
    },

    // Reads an order and locks it until the transaction ends,
    // so two people changing its status cannot collide.
    async lockById(conn, id) {
      const [rows] = await conn.execute(
        `SELECT id, user_id AS userId, status
           FROM orders WHERE id = ? FOR UPDATE`, [id]);
      return rows[0] || null;
    },

    async setStatus(conn, id, status, now) {
      await conn.execute(
        'UPDATE orders SET status = ?, updated_at = ? WHERE id = ?',
        [status, now, id]);
    },

    async findById(id) {
      const [rows] = await pool.execute(
        `SELECT ${ORDER_COLUMNS} ${FROM_ORDERS}
          WHERE o.id = ?`, [id]);
      const [order] = await withItems(pool, rows);
      return order || null;
    },

    async listForUser(userId, date) {
      const [rows] = await pool.execute(
        `SELECT ${ORDER_COLUMNS} ${FROM_ORDERS}
          WHERE o.user_id = ? AND o.pickup_date = ?
          ORDER BY o.pickup_slot, o.id`, [userId, date]);
      return withItems(pool, rows);
    },

    async listForDay(date, slot) {
      const bySlot = slot ? 'AND o.pickup_slot = ?' : '';
      const [rows] = await pool.execute(
        `SELECT ${ORDER_COLUMNS} ${FROM_ORDERS}
          WHERE o.pickup_date = ? ${bySlot}
          ORDER BY o.pickup_slot, o.id`,
        slot ? [date, slot] : [date]);
      return withItems(pool, rows);
    },

    // What the kitchen still has to make for one slot.
    async kitchenSummary(date, slot) {
      const [rows] = await pool.execute(
        `SELECT m.name,
                CAST(SUM(oi.quantity) AS UNSIGNED) AS quantity
           FROM orders o
           JOIN order_items oi ON oi.order_id = o.id
           JOIN menu_items m ON m.id = oi.menu_item_id
          WHERE o.pickup_date = ? AND o.pickup_slot = ?
            AND o.status IN ('placed', 'preparing')
          GROUP BY m.id, m.name
          ORDER BY m.name`, [date, slot]);
      return rows;
    },

    async statusCounts(date) {
      const [rows] = await pool.execute(
        `SELECT status, COUNT(*) AS orders
           FROM orders WHERE pickup_date = ?
          GROUP BY status
          ORDER BY FIELD(status, 'placed', 'preparing', 'ready',
                         'collected', 'cancelled', 'no_show')`,
        [date]);
      return rows;
    },

    // Only collected orders are sales: a cancelled order was
    // never paid for, and a no-show was never paid for either.
    async itemSales(date) {
      const [rows] = await pool.execute(
        `SELECT m.name,
                CAST(SUM(oi.quantity) AS UNSIGNED) AS quantity,
                CAST(SUM(oi.quantity * oi.unit_price_paise)
                     AS UNSIGNED) AS revenuePaise
           FROM orders o
           JOIN order_items oi ON oi.order_id = o.id
           JOIN menu_items m ON m.id = oi.menu_item_id
          WHERE o.pickup_date = ? AND o.status = 'collected'
          GROUP BY m.id, m.name
          ORDER BY revenuePaise DESC, m.name`, [date]);
      return rows;
    },
  };
}

module.exports = { orderStore };
munotes.in303

Database Integration, Part 1: the Connection Pool and Safe Queries

Three things in it are worth copying:

  • withItems fetches the items of many orders in one query. The obvious way, asking for each order's items in turn, is the "N + 1 queries" mistake: forty orders on the counter's screen would be forty-one round trips, every ten seconds. One query with a list of ids, and the rows are hung on their orders in memory.
  • The IN (?, ?, ?) list is built from the number of ids, not from their values: the text has as many question marks as there are orders, and the ids are still passed separately.
  • The column names are translated once, in the SELECT list: o.total_paise AS totalPaise. The database keeps snake_case, the application reads camelCase, and the one name that breaks the rule, pickup_slot read as slot, is the exception Chapters 21 and 24 write down.

Trying it

First, that a placeholder really does keep SQL as text. The owner adds a menu item whose name is an attack, and the orders table is counted before and after:

munotes.in304

Database Integration, Part 1: the Connection Pool and Safe Queries

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s -c owner -H 'Content-Type: application/json' -o /dev/null \
>   -d '{"email":"owner@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ curl -s -b owner -H 'Content-Type: application/json' \
>   -d '{"name":"Tea'"'"'); DROP TABLE orders; --","category":"drinks","pricePaise":1500,"isVeg":true}' \
>   localhost:3000/api/menu | jq -c '.item | {id, name}'
{"id":15,"name":"Tea'); DROP TABLE orders; --"}
$ sudo mysql canteen -e "SELECT name FROM menu_items WHERE id = 15; SHOW TABLES LIKE 'orders';"
name
Tea'); DROP TABLE orders; --
Tables_in_canteen (orders)
orders

The name was stored exactly as it was typed, and the orders table is still there. MySQL never saw those characters as SQL at all; they were a value.

Second, that the pool is a pool. Ten queries at once are answered by different connections:

$ node -e "
> process.loadEnvFile('.env');
> const { createPool } = require('./src/db');
> const { loadConfig } = require('./src/config');
> const pool = createPool(loadConfig(process.env).db);
> (async () => {
>   const ids = await Promise.all(Array.from({ length: 10 }, () =>
>     pool.query('SELECT CONNECTION_ID() AS id')
>       .then(([rows]) => rows[0].id)));
>   console.log('different connections used:', new Set(ids).size);
>   const again = await pool.query('SELECT CONNECTION_ID() AS id');
>   console.log('one more query reuses a connection:',
>     ids.includes(again[0][0].id));
>   await pool.end();
> })();
> "
different connections used: 10
one more query reuses a connection: true

Ten queries sent at once were spread across ten connections, the pool's limit; the eleventh, sent after they had finished, reused one that was already open instead of making another. That is the whole benefit: no connection is opened while the canteen is busy.

Do this for your project

  1. Write one script that creates your databases and the application's own account, and keep a separate database for the tests.
  2. Use utf8mb4, so that every name your users can type can be stored.
  3. Never let the application sign in to the database as the administrator.
  4. Use a pool, and give it a sensible limit.
  5. Put every SQL statement in one layer, and nothing else in it.
  6. Send every value as a placeholder. Where the text of a statement must vary, let it vary only by names taken from a fixed list in your code.
  7. Fetch the children of many rows in one query, not one query for each.

Mistakes that cost marks

SQL built with template strings, and a sign-in that can be passed with ' OR '1'='1.

The application signing in as root, so a bug can drop any database on the server.

A new connection for every request, and a demonstration that slows to a crawl under the load test.

munotes.in305

Database Integration, Part 1: the Connection Pool and Safe Queries

multipleStatements on in the application, turning one injection into several statements.

N + 1 queries: one query for the list, then one for each row, on a page that refreshes every ten seconds.

Tests run against the real database, which empties it in front of the examiner.

Quick revision

  • create-database.sql: two databases (real and test), utf8mb4, an account with rights to those two only.
  • npm run db:setup runs schema.sql then seed.sql; multipleStatements is for the script, never the application.
  • The pool: a few connections lent out; limit 10; dateStrings: true so the application decides what a time means.
  • Every value a ?; the only thing that may vary the SQL text is an identifier from a fixed list in the code.
  • withItems: one query for the items of many orders, not N + 1.
  • Column names translated once, in the SELECT list.

Questions you must be able to answer

1. What is a connection pool, and why use one? A small set of connections to the database, kept open and lent to each query in turn. Opening a connection costs a round trip and an authentication, so opening one per request would add tens of milliseconds to every request and could exhaust the database's own limit under load.

2. How does the application prevent SQL injection? Every value is sent as a ? placeholder with the values passed separately, so MySQL treats them as data whatever characters they contain and they never become part of the statement. Where a statement's text must vary, it varies only by column names taken from a fixed list in the code, never from the request.

3. Why is multipleStatements switched on for the setup script but not for the application? Because a .sql file contains many statements and the script must run them all, while in the application the option would let one injected value carry a second statement, turning a small hole into a large one.

4. What is the N + 1 queries problem, and how does the store avoid it? Fetching a list with one query and then the children of each row with one query each, which is N + 1 round trips for N rows. The store fetches the items of all the orders in one query with a list of ids and attaches them in memory.

5. Why does the pool ask for dates as strings? So that a DATE or DATETIME arrives exactly as it is stored, instead of being converted into a JavaScript date in the server's own time zone. The application is the only clock, and it decides what every time means (ADR-4).

munotes.in306

Database Integration, Part 1: the Connection Pool and Safe Queries

6. Why does the application have its own database account? So that it can reach only its own two databases and nothing else on the server, and only from the machine it runs on. Signing in as the administrator would let any bug, or anyone who found the password, touch every database.

Contents This chapter on its own page

munotes.in307

Chapter Forty-Five

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

Syllabus topic Module 2, "Application Development: Database integration", second part: transactions, and the rule that the last plate is sold once.

In one line

Placing an order is several statements that must all happen or none of them, so they run inside one transaction; and because two students can order the last plate in the same instant, the stock is not read and then written but taken by a single conditional update, which either changes one row or changes nothing and says so.

In the wording to use when asked: a transaction is a unit of work with the ACID properties, atomicity, consistency, isolation and durability, committed as a whole or rolled back entirely; concurrent updates to the same row are made safe either by pessimistic locking, such as SELECT ... FOR UPDATE, or by a conditional update whose WHERE clause carries the precondition, so that the number of rows affected reports whether the operation succeeded.

Why an order needs a transaction

Placing one order touches the database five ways: it locks the student's row, counts their orders for the slot, takes each item's stock, inserts the order, and inserts a line for each item. If the server stopped, or an item ran short, half way through that list, the database would be left holding a truth nobody meant: stock taken for an order that does not exist, or an order with half its items.

A transaction makes the whole list one step. Everything in it is kept, or nothing is. In MySQL that is BEGIN, the statements, and then COMMIT to keep them or ROLLBACK to undo them, and it works because the tables use the InnoDB engine (Chapter 29).

The four letters, in the order they are always given:

PropertyWhat it means here
AAtomicitythe five steps happen as one: all, or none
CConsistencythe database's own rules, the keys and the CHECK constraints, hold before and after
IIsolationanother student's order, running at the same moment, does not see this one's half-finished work
DDurabilityonce the answer says the order is placed, it survives the server being switched off

The application's helper is eight lines, in db.js (Chapter 44):

// Runs work(conn) inside one transaction: every statement in
// it is kept, or, if anything throws, none of them is.
async function withTransaction(pool, work) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    const result = await work(conn);
    await conn.commit();
    return result;
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }
}

Three things it gets right, each of which is a mistake somewhere:

  • One connection for the whole transaction. A pool lends a connection per query, so a transaction that took a new one for each statement would open a transaction on one connection and commit on another, which does nothing at all.
  • Rolled back on any error, not only on the ones the code expected. The refusals the service throws, sold_out and the rest, roll back exactly as a bug would.
  • The connection is released in finally, so a failure does not leak it. Ten leaked connections and the canteen stops serving.
munotes.in308

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

The rule that cannot be tested by hand

FR-9 and NFR-2 say: when many students order the last few portions at the same moment, exactly as many orders as there are portions may be accepted. This is the one requirement no amount of clicking can test, because it is about what happens in the same instant.

The obvious way to write it is the wrong one:

  1. read how many are left;
  2. if there are enough, work out the new number;
  3. write the new number.

Two requests that both run step 1 before either runs step 3 both see the old number, and both write a new one. The second write overwrites the first, and the canteen has sold food it does not have. This is a lost update, and it does not need a busy canteen: two taps in the same tenth of a second are enough.

The application never does that. The store takes stock in one statement:

    // Takes `quantity` from the stock only if that many are
    // left, in ONE statement, so two orders for the last
    // plate cannot both succeed. True if it was taken.
    async takeStock(conn, id, quantity) {
      const [result] = await conn.execute(
        `UPDATE menu_items
            SET stock_left = stock_left - ?
          WHERE id = ? AND is_available = TRUE
            AND stock_left >= ?`,
        [quantity, id, quantity]);
      return result.affectedRows === 1;
    },

The condition is inside the WHERE, so the database itself decides, row by row, whether there is enough: it takes a row lock, applies the condition to the row as it is at that moment, and moves on. Two requests for the last plate are applied one after the other, and the second one's condition is false, so it changes no rows. affectedRows is then 0, and that is how the application learns it did not get the plate. The answer is not read and then decided upon; the decision and the write are the same statement.

That is ADR-5 (Chapter 36), and with it comes the rule that items are taken in the order of their ids, so that two orders sharing items always lock them in the same order and cannot wait for each other for ever.

Seeing it both ways

npm run race does exactly what the two paragraphs above describe, on the test database, and prints what each way sells:

munotes.in309

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

'use strict';

// npm run race
// Shows, on a real database, what the stock rule protects
// against. Twenty students want the last five Veg Biryani at
// the same moment, and the database is asked to sell to them
// in two ways:
//
//   1. read the stock, then write the new stock: the mistake;
//   2. one conditional UPDATE: the application's own takeStock.
//
// It rebuilds the TEST database first and works only there,
// never on the real one, so run it where the tests can run.

const fs = require('node:fs');
const path = require('node:path');
const mysql = require('mysql2/promise');
const { loadConfig } = require('../src/config');
const { createStore } = require('../src/store');
const { resetDatabase } = require('./setup-db');

const VEG_BIRYANI = 2;
const PLATES = 5;
const STUDENTS = 20;

async function stockLeft(pool) {
  const [[row]] = await pool.execute(
    'SELECT stock_left FROM menu_items WHERE id = ?', [VEG_BIRYANI]);
  return row.stock_left;
}

async function setStock(pool, plates) {
  await pool.execute(
    'UPDATE menu_items SET stock_left = ? WHERE id = ?',
    [plates, VEG_BIRYANI]);
}

// The mistake: each request reads the stock, decides, and then
// writes the stock it worked out for itself. Requests that
// arrive together all read before any of them writes.
async function readThenWrite(pool) {
  await setStock(pool, PLATES);
  const conns = await Promise.all(
    Array.from({ length: STUDENTS }, () => pool.getConnection()));
  try {
    const seen = await Promise.all(conns.map(async (conn) => {
      const [[row]] = await conn.execute(
        'SELECT stock_left FROM menu_items WHERE id = ?',
        [VEG_BIRYANI]);
      return row.stock_left;
    }));
    const sold = await Promise.all(conns.map(async (conn, i) => {
      if (seen[i] < 1) return false;
      await conn.execute(
        'UPDATE menu_items SET stock_left = ? WHERE id = ?',
        [seen[i] - 1, VEG_BIRYANI]);
      return true;
    }));
    return sold.filter(Boolean).length;
  } finally {
    conns.forEach((conn) => conn.release());
  }
}

// The application's way: one statement that takes a plate
// only if one is left. The database applies the twenty one
// after another, and every one after the fifth changes nothing.
async function conditionalUpdate(pool) {
  await setStock(pool, PLATES);
  const store = createStore(pool);
  const taken = await Promise.all(Array.from({ length: STUDENTS },
    () => store.menu.takeStock(pool, VEG_BIRYANI, 1)));
  return taken.filter(Boolean).length;
}

async function main() {
  const envFile = path.join(__dirname, '..', '.env');
  if (fs.existsSync(envFile)) process.loadEnvFile(envFile);
  const { db } = loadConfig({ ...process.env,
    DB_NAME: process.env.TEST_DB_NAME || 'canteen_test' });
  await resetDatabase(db);
  // One connection for each student, so that all twenty really
  // are in the database at once.
  const pool = mysql.createPool({
    host: db.host, port: db.port, database: db.database,
    user: db.user, password: db.password,
    connectionLimit: STUDENTS,
  });
  try {
    console.info(`The last ${PLATES} Veg Biryani, and ${STUDENTS} `
      + 'students who each want one, at the same moment.');
    const naive = await readThenWrite(pool);
    console.info(`Read, then write:   ${naive} told yes, `
      + `${await stockLeft(pool)} left in the database`);
    const right = await conditionalUpdate(pool);
    console.info(`Conditional UPDATE: ${right} told yes, `
      + `${await stockLeft(pool)} left in the database`);
  } finally {
    await pool.end();
  }
}

main().catch((err) => {
  console.error('Race test failed:', err.message);
  process.exitCode = 1;
});
munotes.in310

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

$ cd ~/canteen-preorder
$ npm run race

> canteen-preorder@1.0.0 race
> node scripts/race-test.js

The last 5 Veg Biryani, and 20 students who each want one, at the same moment.
Read, then write:   20 told yes, 4 left in the database
Conditional UPDATE: 5 told yes, 0 left in the database

Twenty students, five plates. Read-then-write tells twenty students yes, and leaves the stock at 4, which is nonsense in two directions at once: fifteen students will arrive to nothing, and the database has lost count. The conditional update tells exactly five yes and leaves 0.

The whole order, all or nothing

The service wraps the counting, the taking and the inserting in one transaction (Chapter 43). What that buys is easiest to see when an order fails half way: its first item has been taken, and then the second is short.

  it('keeps nothing of an order that fails half way', async () => {
    await ownerSetsStock(VEG_BIRYANI, 2);
    const res = await priya.post('/api/orders', {
      slot: '12:40',
      items: [
        { menuItemId: VEG_THALI, quantity: 1 },
        { menuItemId: VEG_BIRYANI, quantity: 3 },
      ],
    });
    assert.equal(res.status, 409);
    // The thali was taken first, then given back by the
    // rollback when the biryani failed.
    assert.equal(await stockOf(VEG_THALI), 60);
  });

The thali was taken, the biryani refused, and the rollback put the thali back: the stock is 60, exactly what it was. That is FR-9's "if any item of an order cannot be supplied, no part of the order shall be accepted", enforced by the database rather than by remembering to undo things.

The two concurrency tests

The tests that prove NFR-2 fire their requests together and count what comes back:

'use strict';

// The two rules that only fail when requests arrive at the
// same moment. Each test fires its requests together with
// Promise.all and counts what came back.

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

const VEG_BIRYANI = 2;
const LETTERS = 'ABCDEFGHIJKLMNOPQRST';

describe('orders that arrive at the same moment', () => {
  let app;
  beforeEach(async () => {
    app = await startApp();
  });
  afterEach(() => app.stop());

  it('sells the last 5 plates to exactly 5 of 20 students',
    async () => {
      const lata = app.client();
      await lata.signIn('owner@college.example');
      await lata.put(`/api/menu/${VEG_BIRYANI}/stock`,
        { stockLeft: 5 });

      const students = await Promise.all([...LETTERS].map(
        async (letter) => {
          const name = letter.toLowerCase();
          const email = `student.${name}@college.example`;
          const c = app.client();
          await c.post('/api/auth/register', {
            name: `Student ${letter}`, email, password: 'canteen-demo',
          });
          await c.signIn(email);
          return c;
        }));

      const results = await Promise.all(students.map((c) =>
        c.post('/api/orders', {
          slot: '12:40',
          items: [{ menuItemId: VEG_BIRYANI, quantity: 1 }],
        })));

      const codes = results.map((r) => r.status);
      assert.equal(codes.filter((s) => s === 201).length, 5);
      assert.equal(codes.filter((s) => s === 409).length, 15);
      const menu = (await app.client().get('/api/menu')).body.items;
      assert.equal(menu.find((i) => i.id === VEG_BIRYANI).stockLeft, 0);
    });

  it('accepts only one of two orders one student sends at once',
    async () => {
      const priya = app.client();
      await priya.signIn('priya@college.example');
      const order = {
        slot: '12:40',
        items: [{ menuItemId: VEG_BIRYANI, quantity: 1 }],
      };
      const results = await Promise.all([
        priya.post('/api/orders', order),
        priya.post('/api/orders', order),
      ]);
      assert.deepEqual(results.map((r) => r.status).sort(), [201, 409]);
    });
});
munotes.in311

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

  • Twenty students, five plates: exactly 5 answers are 201 and 15 are 409, and the stock ends at 0. This is the test Chapter 22's sequence diagram promised.
  • One student, two orders at once: exactly one is accepted. FR-10 allows one active order per slot, and the student's row is locked before their orders are counted, so the second request waits for the first to finish and then finds the order it did not see a moment ago.

Promise.all is what makes them concurrent: every request is sent before any answer is read. Running that file alone shows both:

$ cd ~/canteen-preorder
$ node --test --test-reporter=./test/reporter.js \
>   test/integration/concurrency.test.js
test/integration/concurrency.test.js
  pass  sells the last 5 plates to exactly 5 of 20 students
  pass  accepts only one of two orders one student sends at once

2 tests: 2 passed, 0 failed

Where the locks are

Two locks matter, and they are different from each other:

LockWhereWhat it stops
SELECT ... FOR UPDATE on the student's rowlockUser, at the start of placing an ordertwo orders from one student being counted at the same moment (FR-10)
SELECT ... FOR UPDATE on the order's rowlockById, before a status is changedthe counter and the student changing one order's status at the same moment (FR-13, FR-15)

Both hold until the transaction ends. That is why a transaction must be short and must never wait for a person: nothing inside withTransaction asks anyone anything.

The conditional update needs no FOR UPDATE at all, because an UPDATE takes its own row lock while it runs. Either technique would work for the stock; the conditional update is one statement instead of two, and it is the one MySQL applies most cheaply.

MySQL's own default isolation level, REPEATABLE READ, decides what one transaction sees of another's uncommitted work. It is not what makes any of this correct: the two locks and the condition inside the UPDATE are. A design that depends on an isolation level is a design that breaks when somebody changes a setting.

munotes.in312

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

Watching one transaction wait for another

The second lock can be seen working, with two connections and no application at all. One transaction locks the student's row and holds it; the other tries and waits:

$ cd ~/canteen-preorder
$ sudo mysql canteen -e "
> SELECT @@transaction_isolation AS isolation, @@innodb_lock_wait_timeout AS wait_seconds;"
isolation	wait_seconds
REPEATABLE-READ	50
$ (sudo mysql canteen -e "
>   START TRANSACTION;
>   SELECT id FROM users WHERE id = 3 FOR UPDATE;
>   SELECT SLEEP(3);
>   ROLLBACK;" > /dev/null &) ; sleep 1
$ sudo mysql canteen -e "
>   SET innodb_lock_wait_timeout = 1;
>   START TRANSACTION;
>   SELECT id FROM users WHERE id = 3 FOR UPDATE;" 2>&1 | tail -1
ERROR 1205 (HY000) at line 4: Lock wait timeout exceeded; try restarting transaction

The second transaction asked for a row the first was holding, waited its one second, and gave up with MySQL's own message. In the application nothing waits that long, because the transaction it is waiting for is a few milliseconds of work; but the message is worth seeing once, because a student who leaves a transaction open in a MySQL window and then runs the tests will meet it.

Do this for your project

  1. Find the steps in your system that must all happen or none, and put each set in one transaction.
  2. Use one connection for a whole transaction, roll back on any error, and release it in a finally.
  3. For anything limited, stock, seats, places on a list, put the condition inside the UPDATE and read how many rows changed.
  4. Never read a value, decide, and write it back, where two requests could overlap.
  5. Lock rows in a fixed order, so that two transactions cannot wait for each other.
  6. Keep transactions short, and never wait for a person inside one.
  7. Test it by firing the requests together and counting the answers; a test that sends them one after another proves nothing.

Mistakes that cost marks

Read, decide, write. It works in every demonstration and fails on the day the canteen is busy.

A transaction on a pool, so BEGIN and COMMIT go to different connections and nothing is transactional.

A connection taken and never released, until the tenth failure stops the canteen.

A transaction that waits for a person, holding a lock through a confirmation dialog.

Concurrency "tested" one request at a time, which can never fail.

Trusting an isolation level to prevent a lost update that nothing in the code prevents.

Quick revision

  • ACID: atomicity, consistency, isolation, durability.
  • withTransaction: one connection, beginTransaction, commit, rollback on any error, release in finally.
  • Lost update: two requests read, both write, one is lost. The cure is the condition inside the UPDATE and affectedRows.
  • ADR-5: one conditional update, inside a transaction; items taken in id order so nothing deadlocks.
  • FOR UPDATE on the student's row (FR-10) and on the order's row (FR-13, FR-15), held to the end of the transaction.
  • Proof: 20 students, 5 plates, 5 accepted; two orders at once, one accepted; a failed order leaves the stock untouched.
munotes.in313

Database Integration, Part 2: Transactions and the Order That Must Not Oversell

Questions you must be able to answer

1. What is a transaction, and what are its four properties? A unit of work that the database keeps as a whole or undoes as a whole. Atomicity: all of it or none. Consistency: the database's rules hold before and after. Isolation: another transaction running at the same time does not see its half-finished work. Durability: once committed, it survives a crash.

2. What is a lost update, and how does the application prevent it? Two requests read the same value, each works out a new one from what it read, and both write: the second overwrites the first, and one of the changes is lost. The application never reads and then writes; it takes stock with a single UPDATE whose WHERE clause carries the condition, and reads the number of rows changed to learn whether it succeeded.

3. Why does the number of rows affected matter? Because it is the database's own answer to whether the condition held. One row changed means the stock was taken; no rows changed means there was not enough, and no stock has been touched.

4. Why must a transaction use one connection? Because a transaction is a property of a connection: BEGIN on one connection and COMMIT on another would commit nothing, and the statements in between would each be committed on their own.

5. Why are an order's items taken in the order of their ids? So that two orders that share items always lock those items in the same order. If one took A then B and the other B then A, each could hold what the other needs, and neither could go on.

6. How can a rule about simultaneous requests be tested? By sending the requests together, without waiting for the answers, and counting what comes back: twenty students ordering the last five plates must produce exactly five acceptances and fifteen refusals, and the stock must end at zero.

Contents This chapter on its own page

munotes.in314

Chapter Forty-Six

Authentication: Passwords, Sessions and Roles

Syllabus topic Module 2, "Application Development: Authentication & validation", the authentication half: registering, signing in, sessions and roles.

In one line

Authentication is proving who someone is and remembering it: a password stored only as a slow, salted hash that can never be read back, a sign-in that answers the same way whatever is wrong with it and is limited after five failures, a random token in a cookie the page's scripts cannot read, of which the database keeps only a hash, and a role on every request that decides what that person may do.

In the wording to use when asked: authentication verifies a principal's identity; authorisation decides what that principal may do. Passwords are stored as salted hashes produced by a deliberately slow, memory-hard function, never encrypted or digested with a fast hash; sessions are represented by high-entropy tokens transmitted in HttpOnly, SameSite cookies and stored only as digests; and authorisation is enforced on the server for every request, by role and by ownership.

Registering

FR-1: a name, an email address at the college's own domain, and a password of 8 to 128 characters that is not a common one. The rules live in the validation (Chapter 47), and the domain check in the service (Chapter 43):

  async function register(details) {
    const domain = details.email.split('@')[1];
    if (domain !== config.emailDomain) {
      throw badRequest('Some details need correcting.', {
        email: `Use your @${config.emailDomain} address.`,
      });
    }
    return create(details, 'student');
  }

Registering always makes a student. There is no field in the request that could ask for another role: counter staff are created by the owner, through a different route, and the owner's account is made when the system is set up. A role that a request can choose is a role an attacker can choose.

Storing a password

A password is never stored, and never encrypted either: encryption can be undone, and whoever holds the key can read every password. What is stored is a hash: a value worked out from the password that cannot be turned back into it. A sign-in hashes what was typed and compares.

Two things make that safe:

  • A salt: 16 random bytes, different for every password, mixed in before hashing. Without one, two students who chose the same password would have the same hash, and an attacker could hash a list of common passwords once and look up every match. With one, the work must be done again for every single password.
  • A slow, memory-hard function. A fast hash such as SHA-256 can be tried billions of times a second on a graphics card. OWASP's Password Storage Cheat Sheet asks for something deliberately slow that also needs a lot of memory, which makes those cards far less useful. It ranks Argon2id first and scrypt where Argon2id is not available.

The worked application uses scrypt, and Chapter 36 records why: Node.js added Argon2 to its own crypto module only in version 24.7.0, and NFR-12 supports Node.js 22, where it does not exist. The settings are one of the five combinations the cheat sheet lists as equally strong.

munotes.in315

Authentication: Passwords, Sessions and Roles

'use strict';

const crypto = require('node:crypto');
const { promisify } = require('node:util');

const scrypt = promisify(crypto.scrypt);

// One of the five equal-strength scrypt settings in the
// OWASP Password Storage Cheat Sheet: N = 2^14, r = 8,
// p = 5. It needs 16 MiB, within Node's default limit.
const COST = { N: 16384, r: 8, p: 5 };
const KEY_BYTES = 32;
const SALT_BYTES = 16;

// Returns "scrypt$N$r$p$salt$key", salt and key in base64.
// The settings travel with the hash, so they can be raised
// later without breaking the passwords already stored.
async function hashPassword(password) {
  const salt = crypto.randomBytes(SALT_BYTES);
  const key = await scrypt(password, salt, KEY_BYTES, COST);
  return [
    'scrypt', COST.N, COST.r, COST.p,
    salt.toString('base64'), key.toString('base64'),
  ].join('$');
}

async function verifyPassword(password, stored) {
  const parts = String(stored).split('$');
  if (parts.length !== 6 || parts[0] !== 'scrypt') {
    return false;
  }
  const [, N, r, p, saltText, keyText] = parts;
  const salt = Buffer.from(saltText, 'base64');
  const expected = Buffer.from(keyText, 'base64');
  const cost = { N: Number(N), r: Number(r), p: Number(p) };
  const key = await scrypt(password, salt, expected.length, cost);
  // Compares every byte whatever happens, so the time taken
  // does not tell an attacker how much of a guess was right.
  return crypto.timingSafeEqual(key, expected);
}

module.exports = { hashPassword, verifyPassword };
  • The settings travel with the hash: scrypt$16384$8$5$salt$key. Raising the cost later, or moving to Argon2id, is then possible without resetting anybody's password: each hash says how it was made, and a sign-in can re-hash with the new settings once the old one has verified.
  • timingSafeEqual compares every byte whatever happens. An ordinary comparison stops at the first difference, and the time it takes can tell an attacker how much of a guess was right.
  • Nothing here logs anything. A password must never reach a log file (S3).

What it produces, and that the salt really does its work:

$ cd ~/canteen-preorder
$ node -e "
> const { hashPassword, verifyPassword } = require('./src/passwords');
> (async () => {
>   const a = await hashPassword('the same password');
>   const b = await hashPassword('the same password');
>   const part = (h) => h.split('\$');
>   console.log('settings:', part(a).slice(0, 4).join('\$'));
>   console.log('salt and key, in bytes:',
>     Buffer.from(part(a)[4], 'base64').length,
>     Buffer.from(part(a)[5], 'base64').length);
>   console.log('two hashes of one password are equal?', a === b);
>   console.log('right password verifies?',
>     await verifyPassword('the same password', a));
>   console.log('wrong password verifies?',
>     await verifyPassword('the same password ', a));
>   const t = Date.now();
>   await hashPassword('timing');
>   console.log('one hash takes about', Date.now() - t, 'ms');
> })();
> "
settings: scrypt$16384$8$5
salt and key, in bytes: 16 32
two hashes of one password are equal? false
right password verifies? true
wrong password verifies? false
one hash takes about 163 ms
munotes.in316

Authentication: Passwords, Sessions and Roles

The two hashes of one password are different, because the salts are. And a hash takes a noticeable fraction of a second on purpose: an attacker who steals the table must spend that on every guess, while a student signing in spends it once.

Which passwords are refused

ASVS 5.0 level 1 asks for three things, and the application does all three (Chapter 31):

ASVSWhat it asksHere
6.2.1at least 8 characters, 15 strongly recommended8 to 128, with the gap from NIST's 15 recorded as accepted
6.2.4refuse at least the 3,000 most common passwords that fit the policythe 10,000 most common of 8 to 128 characters
6.2.5no composition rulesnone: any characters at all

No composition rules is worth dwelling on, because it looks like laxity and is the opposite. Demanding a capital, a digit and a symbol makes passwords harder to remember and barely harder to guess: people answer it with Password1!, which is on every attacker's list. Refusing the common ones attacks the same problem where it actually is.

The check itself is four lines of the validation, with the list read once at start-up:

// The 10,000 most common passwords of 8 to 128 characters,
// read once at start-up (data/README.md says where they come
// from). A password on this list is among the first an
// attacker tries, so it is refused whatever its length.
const COMMON = new Set(fs.readFileSync(
  path.join(__dirname, '..', 'data', 'common-passwords.txt'),
  'utf8').split('\n').filter(Boolean));
function checkPassword(p, password) {
  if (typeof password !== 'string'
      || password.length < 8 || password.length > 128) {
    p.add('password', 'Use a password of 8 to 128 characters.');
  } else if (COMMON.has(password)
      || COMMON.has(password.toLowerCase())) {
    p.add('password', 'This password is too common. '
      + 'Choose one that is hard to guess.');
  }
}

A Set answers in the same time whether it holds 10 or 10,000 words, so the check costs nothing; and the lower-cased form is checked too, so PassWord1 is refused along with password1.

Where the list comes from matters as much as having one. A list of passwords downloaded from anywhere is not evidence; this one is named, licensed and reproducible:

# data/common-passwords.txt

The 10,000 most commonly used passwords that are 8 to 128 characters long,
the lengths the application would otherwise accept, one to a line, the most
common first. Registration and new staff accounts refuse every one of them
(SRS FR-1, security control S5). OWASP's Application Security Verification
Standard 5.0, requirement 6.2.4, asks for at least the 3,000 most common.

## Where it comes from

SecLists, the collection of security testing lists by Daniel Miessler and its
contributors, file
`Passwords/Common-Credentials/xato-net-10-million-passwords-100000.txt`:
100,000 different passwords from Mark Burnett's ten million passwords
dataset of 2015, sorted from most to least common. Downloaded on
30 September 2026, SHA-256
`1472aafa2561df5e3293aee252aee3ca660c12b399a283cf808bb01b39be388b`.

Made with:

    curl -fsSLo xato.txt https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/xato-net-10-million-passwords-100000.txt
    tr -d '\r' < xato.txt | awk 'length >= 8 && length <= 128' | head -n 10000 > data/common-passwords.txt

## Licence

SecLists is released under the MIT License:

    MIT License

    Copyright (c) 2018 Daniel Miessler

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
munotes.in317

Authentication: Passwords, Sessions and Roles

The registration page asks for a password used nowhere else, in the label itself so that a screen reader reads it with the field (Chapter 40):

      <label for="register-password">Password: 8 or more
        characters, and not one you use anywhere else</label>

And the refusal, seen from outside:

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s -H 'Content-Type: application/json' \
>   -d '{"name":"Meera Joshi","email":"meera@college.example","password":"sunshine1"}' \
>   localhost:3000/api/auth/register | jq -c .error.details
{"password":"This password is too common. Choose one that is hard to guess."}
$ curl -s -H 'Content-Type: application/json' \
>   -d '{"name":"Meera Joshi","email":"meera@college.example","password":"peas-under-the-bench"}' \
>   localhost:3000/api/auth/register | jq -c .user
{"id":7,"name":"Meera Joshi","email":"meera@college.example","role":"student"}
$ wc -l < data/common-passwords.txt
10000

Signing in

login in the auth service (Chapter 43) does five things in order, and each is a control:

  1. The limit first. The key is the address the request came from and the email tried, and five failures in fifteen minutes gives 429 with the seconds to wait (NFR-6, S4). Keying on both means one noisy network cannot lock out every account, and one attacker cannot try a thousand passwords for one student.
  2. Find the user, or, if there is none, use a dummy hash prepared at start-up, so that the sign-in takes the same time either way (S16).
  3. Verify, in constant time.
  4. One answer for every failure: a wrong password, an unknown email and a switched-off account all give 401 wrong_credentials with the same sentence. An attacker learns nothing about who has an account.
  5. Make the session: 32 random bytes, and only its SHA-256 in the database (S6, ADR-3).
munotes.in318

Authentication: Passwords, Sessions and Roles

The limiter of step 1 is a file of its own, with no database and no clock of its own, which is what lets the tests wind it forward without waiting fifteen minutes:

'use strict';

// Counts failed sign-ins per key (the address the request
// came from, plus the email tried) and refuses a key that has
// failed `max` times inside `windowMs`. The clock is passed
// in so that a test can move time forward without waiting.
//
// It is kept in memory, so it forgets everything when the
// server restarts and it would not be shared between two
// servers. For one canteen server that is enough; the SRS
// records the limit.
function attemptLimiter({ max, windowMs, clock = Date.now }) {
  const failures = new Map();

  function recent(key) {
    const since = clock() - windowMs;
    const times = (failures.get(key) || []).filter((t) => t > since);
    if (times.length > 0) failures.set(key, times);
    else failures.delete(key);
    return times;
  }

  return {
    // Seconds until this key may try again; 0 if it may now.
    blockedFor(key) {
      const times = recent(key);
      if (times.length < max) return 0;
      return Math.ceil((times[0] + windowMs - clock()) / 1000);
    },
    fail(key) {
      failures.set(key, [...recent(key), clock()]);
    },
    reset(key) {
      failures.delete(key);
    },
  };
}

module.exports = { attemptLimiter };

It keeps its counts in memory, which is the honest choice for one server and a recorded limitation for more than one: a second copy of the application would count its own failures and not the first's. Restarting the server forgives everybody, which is a cost worth naming in the report.

  async function login({ email, password }, ip) {
    const key = `${ip}|${email}`;
    const wait = attempts.blockedFor(key);
    if (wait > 0) throw tooManyAttempts(wait);
    const user = await store.users.findByEmail(email);
    const hash = user ? user.passwordHash : await noSuchUser;
    const ok = await verifyPassword(password, hash);
    if (!user || !ok || !user.isActive) {
      attempts.fail(key);
      throw new AppError(401, 'wrong_credentials',
        'The email or the password is wrong.');
    }
    attempts.reset(key);
    const token = crypto.randomBytes(32).toString('base64url');

That the times really are alike can be measured:

$ cd ~/canteen-preorder
$ node -e "
> const run = async (email) => {
>   const t = Date.now();
>   await fetch('http://localhost:3000/api/auth/login', {
>     method: 'POST', headers: { 'Content-Type': 'application/json' },
>     body: JSON.stringify({ email, password: 'not the password' }),
>   });
>   return Date.now() - t;
> };
> (async () => {
>   await run('warm@college.example');
>   console.log('a real student, wrong password:',
>     await run('kabir@college.example'), 'ms');
>   console.log('nobody with that address:   ',
>     await run('nobody@college.example'), 'ms');
> })();
> "
a real student, wrong password: 158 ms
nobody with that address:    145 ms
munotes.in319

Authentication: Passwords, Sessions and Roles

Both take about as long as one scrypt hash, because both do one. Without the dummy hash the second would answer in a millisecond, and an attacker could sort the college's addresses into those that have accounts and those that do not, simply by timing.

The cookie, and the session

function cookieOptions(config) {
  return {
    httpOnly: true, // page scripts cannot read the cookie
    sameSite: 'lax', // not sent on another site's form posts
    secure: config.cookieSecure, // HTTPS only, once it is on
    maxAge: config.sessionHours * 60 * 60 * 1000,
    path: '/',
  };
}

Each flag answers a threat (NFR-4):

  • HttpOnly: no script can read the cookie, so even a script injected into the page could not steal the sign-in.
  • SameSite=Lax: the browser does not send it with a form posted from another site, which is one of three defences against cross-site request forgery (Chapter 31, S7).
  • Secure, whenever the site uses HTTPS: the cookie is then never sent over plain HTTP. On the college Wi-Fi trial it is off, because there is no HTTPS, and that is the trial's recorded weakness (Chapter 25).
  • Eight hours, and the row is deleted at sign-out, so a cookie copied from a shared laptop stops working.

On every request one middleware turns the cookie into a person:

'use strict';

// Reads the Cookie header into { name: value }.
function parseCookies(header) {
  const cookies = {};
  for (const part of (header || '').split(';')) {
    const at = part.indexOf('=');
    if (at < 1) continue;
    const name = part.slice(0, at).trim();
    const value = part.slice(at + 1).trim();
    try {
      cookies[name] = decodeURIComponent(value);
    } catch {
      cookies[name] = value;
    }
  }
  return cookies;
}

// Looks up who is signed in, from the "sid" cookie, and puts
// that person on req.user (or null) for the routes to read.
function loadSession(auth) {
  return async (req, res, next) => {
    const token = parseCookies(req.get('Cookie')).sid;
    req.sessionToken = token;
    req.user = token ? await auth.userFromToken(token) : null;
    next();
  };
}

module.exports = { parseCookies, loadSession };

It never refuses anything: req.user is either a user or null, and the guards decide. A session whose row has expired or whose user has been switched off simply returns null, because the query that looks it up says so (Chapter 44).

munotes.in320

Authentication: Passwords, Sessions and Roles

Roles: what each person may do

The role is on the user, and two guards use it (Chapter 42):

'use strict';

const { notSignedIn, forbidden } = require('../errors');

// Placed in front of a route: only signed-in users get past.
function requireUser(req, res, next) {
  next(req.user ? undefined : notSignedIn());
}

// Only signed-in users with one of these roles get past.
function requireRole(...roles) {
  return (req, res, next) => {
    if (!req.user) return next(notSignedIn());
    if (!roles.includes(req.user.role)) return next(forbidden());
    next();
  };
}

module.exports = { requireUser, requireRole };

Role is not enough. Being a student does not entitle anyone to this order: the service checks the owner of the row as well, and answers "not found" for someone else's (S1). Role answers "may this kind of person do this kind of thing"; ownership answers "is this their thing".

The whole ladder, from outside:

$ cd ~/canteen-preorder
$ curl -s -o /dev/null -w "nobody signed in:      %{http_code}\n" \
>   localhost:3000/api/reports/daily
nobody signed in:      401
$ curl -s -c priya -H 'Content-Type: application/json' -o /dev/null \
>   -d '{"email":"priya@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ curl -s -b priya -o /dev/null -w "a student:             %{http_code}\n" \
>   localhost:3000/api/reports/daily
a student:             403
$ curl -s -c lata -H 'Content-Type: application/json' -o /dev/null \
>   -d '{"email":"owner@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ curl -s -b lata -o /dev/null -w "the owner:             %{http_code}\n" \
>   localhost:3000/api/reports/daily
the owner:             200
$ curl -s -b priya localhost:3000/api/auth/me | jq -c .user
{"id":3,"name":"Priya Menon","email":"priya@college.example","role":"student"}

401 with nobody signed in, 403 for the wrong role, 200 for the owner: the API table of Chapter 30, enforced. And me never carries the password hash, because the service returns only what publicUser builds.

Do this for your project

  1. Store passwords only as salted hashes from a slow, memory-hard function, and keep the settings with each hash.
  2. Compare hashes in constant time, and never log a password or a cookie.
  3. Refuse the common passwords, from a named and licensed list, and set no composition rules.
  4. Give every failed sign-in the same answer, and do the same work whether the account exists or not.
  5. Limit failed sign-ins by address and account together.
  6. Use a long random session token, store only its hash, and set HttpOnly, SameSite and Secure with an expiry.
  7. Decide a role on the server for every request, and check ownership as well as role.

Mistakes that cost marks

Passwords stored as they were typed, or encrypted, or hashed with SHA-256 or MD5, with no salt.

"Wrong password" and "no such user" as different messages, which hands an attacker a list of accounts.

munotes.in321

Authentication: Passwords, Sessions and Roles

A role sent by the browser, in the request or in a cookie the page can write.

A session token that is the user's id, or a number that counts up.

No limit on sign-in attempts, so a script can try a million passwords overnight.

Composition rules that produce Password1! and a sticky note on the counter.

Quick revision

  • Hash, never store or encrypt: scrypt (OWASP settings), a 16-byte salt each, settings inside the hash, timingSafeEqual.
  • ASVS 5.0: 6.2.1 8 minimum (15 recommended), 6.2.4 refuse the common ones, 6.2.5 no composition rules.
  • Sign-in: limit first (address and email, 5 in 15 minutes), dummy hash for unknown emails, one message for every failure.
  • Session: 32 random bytes, only the SHA-256 stored; cookie HttpOnly, SameSite=Lax, Secure under HTTPS, 8 hours.
  • Guards: 401 with nobody signed in, 403 for the wrong role; ownership is checked separately.

Questions you must be able to answer

1. Why is a password hashed rather than encrypted? Because encryption can be undone by whoever holds the key, so a stolen database and a stolen key give up every password. A hash cannot be turned back at all: the system only ever hashes what was typed and compares.

2. What does the salt do? It makes every stored hash different, even for two people who chose the same password, so an attacker cannot hash a list of likely passwords once and match it against the whole table, and must attack each password separately.

3. Why use a deliberately slow function such as scrypt? Because the attacker's work is millions of guesses and the user's is one. A fast hash can be tried billions of times a second on a graphics card; a slow, memory-hard one reduces that to a rate at which guessing a good password is hopeless, while a sign-in still takes a fraction of a second.

4. Why does the sign-in check a dummy hash when the email is unknown? So that the request takes the same time whether the account exists or not. Otherwise an attacker could tell which addresses have accounts by timing the answers, even though the message is the same.

5. Why does the database store only a hash of the session token? Because the token in the cookie is what proves who you are. If the sessions table were copied, stored tokens could be used to sign in as anybody; a hash cannot be turned back into the token, so the copy is useless.

6. Is hiding the Owner link on the counter's page a security measure? No. It is a convenience, so that staff do not see a page that is not theirs. The owner's routes check the role on the server for every request, and would refuse anyone else whatever the page showed.

Contents This chapter on its own page

munotes.in322

Chapter Forty-Seven

Validation: Checking Every Input on the Server

Syllabus topic Module 2, "Application Development: Authentication & validation", the validation half: every input checked on the server.

In one line

Validation is the server's own check of everything that arrives, whatever the page already checked: that each value is of the right kind, within its limits and one of the values allowed, gathered into one refusal that names every field at fault, before any rule or any query sees it.

In the wording to use when asked: input validation is performed server-side on every request, independently of any client-side checks, and applies positive validation: each field is checked for type, length or range, and membership of an allowed set, with identifiers and dates parsed strictly; failures are reported together as a structured, per-field response, and the database's own constraints provide a final layer.

Why the server checks what the page already checked

Chapter 40's forms carry required, minlength and type="number". They are worth having: they tell a student what is wanted before anything is sent. They protect nothing, because a request does not need a page. Anyone can send one from a terminal, as this book does on every page, and the browser's rules are then simply not there.

So the server checks everything, and the order of the layers is deliberate:

LayerWhat it catchesWho it helps
The page's own attributesa mistake, before sendingthe student
The server's validationanything of the wrong kind, size or valuethe system
The rules and serviceswhat is wrong now: a closed slot, too little stockthe system
The database's constraintsanything that got past the codethe data

Notice the third and fourth. A quantity of 7 is refused by validation, because the limit is 5 whatever the state of the canteen; an order for a sold-out item is refused by the service, because tomorrow the same request is fine. And CHECK (quantity BETWEEN 1 AND 5) in the schema means that even a bug in the application cannot write a bad row.

The whole module

'use strict';

// Every request body and query the server accepts is checked
// here before any other code sees it. The browser checks too,
// but only to help the user: anyone can send a request
// without a browser, so these checks are the ones that count.

const fs = require('node:fs');
const path = require('node:path');
const { badRequest, notFound } = require('./errors');
const { SLOTS, STATUSES, MAX_PER_ITEM, MAX_ITEMS_PER_ORDER } =
  require('./rules');

const CATEGORIES = ['meals', 'snacks', 'drinks', 'desserts'];
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// Letters of any script, and the marks some scripts need.
const NAME = /^[\p{L}\p{M}][\p{L}\p{M} .'-]*$/u;
const MAX_ID = 4294967295; // the largest INT UNSIGNED

// The 10,000 most common passwords of 8 to 128 characters,
// read once at start-up (data/README.md says where they come
// from). A password on this list is among the first an
// attacker tries, so it is refused whatever its length.
const COMMON = new Set(fs.readFileSync(
  path.join(__dirname, '..', 'data', 'common-passwords.txt'),
  'utf8').split('\n').filter(Boolean));

// Gathers every problem, so the user hears of them all at
// once instead of fixing one and meeting the next.
function problems() {
  const fields = {};
  return {
    add(field, message) {
      if (!(field in fields)) fields[field] = message;
    },
    check() {
      if (Object.keys(fields).length > 0) {
        throw badRequest('Some details need correcting.', fields);
      }
    },
  };
}

function asObject(value) {
  const ok = value !== null && typeof value === 'object'
    && !Array.isArray(value);
  return ok ? value : {};
}

function text(value) {
  return typeof value === 'string' ? value.trim() : '';
}

function isId(value) {
  return Number.isInteger(value) && value >= 1 && value <= MAX_ID;
}

function checkName(p, name) {
  if (name.length < 2 || name.length > 80 || !NAME.test(name)) {
    p.add('name', 'Enter a name of 2 to 80 letters.');
  }
}

function checkEmail(p, email) {
  if (email.length > 120 || !EMAIL.test(email)) {
    p.add('email', 'Enter a valid email address.');
  }
}

function checkPassword(p, password) {
  if (typeof password !== 'string'
      || password.length < 8 || password.length > 128) {
    p.add('password', 'Use a password of 8 to 128 characters.');
  } else if (COMMON.has(password)
      || COMMON.has(password.toLowerCase())) {
    p.add('password', 'This password is too common. '
      + 'Choose one that is hard to guess.');
  }
}

function account(body) {
  const b = asObject(body);
  const p = problems();
  const name = text(b.name);
  const email = text(b.email).toLowerCase();
  checkName(p, name);
  checkEmail(p, email);
  checkPassword(p, b.password);
  p.check();
  return { name, email, password: b.password };
}

function login(body) {
  const b = asObject(body);
  const p = problems();
  const email = text(b.email).toLowerCase();
  if (email === '') p.add('email', 'Enter your email address.');
  if (typeof b.password !== 'string' || b.password === ''
      || b.password.length > 128) {
    p.add('password', 'Enter your password.');
  }
  p.check();
  return { email, password: b.password };
}

function order(body) {
  const b = asObject(body);
  const p = problems();
  if (!SLOTS.includes(b.slot)) {
    p.add('slot', `Choose one of ${SLOTS.join(', ')}.`);
  }
  const lines = Array.isArray(b.items) ? b.items : [];
  if (lines.length === 0) p.add('items', 'Add at least one item.');
  const seen = new Set();
  let count = 0;
  lines.forEach((raw, i) => {
    const line = asObject(raw);
    if (!isId(line.menuItemId)) {
      p.add(`items.${i}.menuItemId`, 'Unknown item.');
    } else if (seen.has(line.menuItemId)) {
      p.add(`items.${i}.menuItemId`, 'This item is listed twice.');
    } else {
      seen.add(line.menuItemId);
    }
    const q = line.quantity;
    if (!Number.isInteger(q) || q < 1 || q > MAX_PER_ITEM) {
      p.add(`items.${i}.quantity`, `Choose 1 to ${MAX_PER_ITEM}.`);
    } else {
      count += q;
    }
  });
  if (count > MAX_ITEMS_PER_ORDER) {
    p.add('items',
      `At most ${MAX_ITEMS_PER_ORDER} items in one order.`);
  }
  p.check();
  return {
    slot: b.slot,
    items: lines.map((l) => ({
      menuItemId: l.menuItemId, quantity: l.quantity,
    })),
  };
}

// For a new item every field is needed; for a change
// (partial) any of them may be given, but at least one.
function menuItem(body, { partial = false } = {}) {
  const b = asObject(body);
  const p = problems();
  const out = {};
  const given = (key) => b[key] !== undefined;
  if (!partial || given('name')) {
    out.name = text(b.name);
    if (out.name.length < 2 || out.name.length > 60) {
      p.add('name', 'Enter a name of 2 to 60 characters.');
    }
  }
  if (!partial || given('category')) {
    out.category = b.category;
    if (!CATEGORIES.includes(b.category)) {
      p.add('category', `Choose one of ${CATEGORIES.join(', ')}.`);
    }
  }
  if (!partial || given('pricePaise')) {
    out.pricePaise = b.pricePaise;
    const price = b.pricePaise;
    if (!Number.isInteger(price) || price < 100
        || price > 100000) {
      p.add('pricePaise', 'Enter a price from 100 to 100000 paise.');
    }
  }
  for (const key of ['isVeg', 'isAvailable']) {
    if (given(key) || (!partial && key === 'isVeg')) {
      out[key] = b[key];
      if (typeof b[key] !== 'boolean') p.add(key, 'Choose yes or no.');
    }
  }
  if (partial && Object.keys(out).length === 0) {
    p.add('body', 'Send at least one field to change.');
  }
  p.check();
  return out;
}

function stock(body) {
  const b = asObject(body);
  const p = problems();
  const n = b.stockLeft;
  if (!Number.isInteger(n) || n < 0 || n > 1000) {
    p.add('stockLeft', 'Enter a whole number from 0 to 1000.');
  }
  p.check();
  return { stockLeft: n };
}

function status(body) {
  const b = asObject(body);
  const p = problems();
  if (!STATUSES.includes(b.status)) {
    p.add('status', `Choose one of ${STATUSES.join(', ')}.`);
  }
  p.check();
  return { status: b.status };
}

// An id in the address, such as the 17 in /api/orders/17.
// Anything that is not a plain whole number cannot name a
// row, so it is reported as not found, not as a bad request.
function idParam(value, what) {
  const n = /^[1-9][0-9]{0,9}$/.test(value) ? Number(value) : 0;
  if (!isId(n)) throw notFound(what);
  return n;
}

// A date such as 2026-02-30 matches the pattern but is not a
// real day, so it is turned into a Date and back to be sure.
function dateQuery(value, fallback) {
  if (value === undefined) return fallback;
  let ok = typeof value === 'string'
    && /^\d{4}-\d{2}-\d{2}$/.test(value);
  if (ok) {
    const day = new Date(`${value}T00:00:00Z`);
    ok = !Number.isNaN(day.getTime())
      && day.toISOString().startsWith(value);
  }
  if (!ok) {
    throw badRequest('Some details need correcting.',
      { date: 'Use a real date written as YYYY-MM-DD.' });
  }
  return value;
}

function slotQuery(value, { required = false } = {}) {
  if (value === undefined && !required) return undefined;
  if (!SLOTS.includes(value)) {
    throw badRequest('Some details need correcting.',
      { slot: `Choose one of ${SLOTS.join(', ')}.` });
  }
  return value;
}

module.exports = {
  CATEGORIES, account, login, order, menuItem, stock, status,
  idParam, dateQuery, slotQuery,
};
munotes.in323

Validation: Checking Every Input on the Server

Every problem at once

problems() gathers the faults and throws one error carrying them all, so a student who has three fields wrong is told about three fields, not one, then the next after they fix it. The error is badRequest, so it reaches the browser as 400 invalid_input with details keyed by field (Chapter 30), and each message lands beside its own field on the page (Chapter 41).

munotes.in324

Validation: Checking Every Input on the Server

The field names are the page's field names, which is what lets the page place them, and for an order's lines they are paths: items.0.quantity. That form is worth copying for any list.

munotes.in325

Validation: Checking Every Input on the Server

Positive validation

Every check says what is allowed, not what is forbidden:

  • A type check first. Number.isInteger(q) refuses "3", 3.5, NaN and null alike. In JSON a number is a number and a string is a string, and the server does not convert one to the other: "3" for a quantity is a mistake in the page, and hiding it would hide the bug.
  • A range or a length, from the rules where the rule has a number: MAX_PER_ITEM, MAX_ITEMS_PER_ORDER, the slots. The 5 and the 10 are not written here at all (Chapter 43).
  • Membership of a list for anything with a fixed set of values: the categories, the statuses, the slots.
  • A pattern only where a pattern is right: the email, and the name, which allows letters of any script, with the marks some scripts need, and the few punctuation marks names really use. A student named प्रिया मेनन registers as easily as one named Priya Menon, and a unit test proves it.

Trying to list what is forbidden instead, the apostrophes, the angle brackets, the word script, is the losing game: the list is never complete, and it refuses real names. Injection is prevented where it happens, by placeholders in SQL (Chapter 44) and by adding text as text in the page (Chapter 41).

Ids and dates

Two kinds of input come from the address rather than the body, and each has a trap:

  • idParam answers notFound rather than badRequest. /api/orders/abc names nothing, and "there is no such order" is both true and the answer that reveals least. The pattern refuses a leading zero and anything above ten digits, so nothing odd reaches the database.
  • dateQuery does not trust a pattern. 2026-02-30 matches \d{4}-\d{2}-\d{2} perfectly and is not a day: the check turns it into a date and back, and only accepts it if it comes out the same.

What the validator returns

Every function returns a new object built from the values it accepted, never the body it was given. So a request with extra fields in it, {"slot":"12:40","items":[...],"role":"owner"}, cannot smuggle anything past: role is simply not in what the service receives. That is worth stating as a rule of its own: take the fields you want, never take the object.

munotes.in326

Validation: Checking Every Input on the Server

The sweep

A validator is only as good as the inputs it has seen. Rather than trust it, send every endpoint the kinds of value that break careless code, and look at what comes back. Nothing should ever be a 500: that would mean the application met something it did not expect.

// A short sweep of the kinds of value that break careless
// code. Nothing here should ever answer 500.
const BASE = 'http://127.0.0.1:3000';
const jar = {};

async function send(who, method, path, body, raw) {
  const headers = {};
  if (jar[who]) headers.Cookie = jar[who];
  let payload;
  if (raw) {
    headers['Content-Type'] = raw.type;
    payload = raw.body;
  } else if (method !== 'GET') {
    headers['Content-Type'] = 'application/json';
    payload = JSON.stringify(body ?? {});
  }
  const res = await fetch(BASE + path, { method, headers, body: payload });
  const set = res.headers.get('set-cookie');
  if (set) jar[who] = set.split(';')[0];
  const data = await res.json().catch(() => null);
  return { status: res.status, code: data?.error?.code ?? '' };
}

const signIn = (who, email) => send(who, 'POST', '/api/auth/login',
  { email, password: 'canteen-demo' });

const WEIRD = [null, true, 0, -1, 1.5, 'abc', [], {}, 'x'.repeat(200),
  "' OR 1=1 --", '<script>x</script>', 1e308, 4294967296];

// Keeps the printed table narrow enough to read on a phone.
const short = (v) => {
  const s = JSON.stringify(v);
  return s.length <= 16 ? s : `${s.slice(0, 13)}..."`;
};

(async () => {
  await signIn('priya', 'priya@college.example');
  await signIn('lata', 'owner@college.example');
  const answers = [];
  const probe = async (label, ...args) => {
    const { status, code } = await send(...args);
    const answer = `${status} ${code}`.trim();
    answers.push(answer);
    console.log(`${label.padEnd(24)} ${answer}`);
  };
  for (const w of WEIRD) {
    await probe(`quantity ${short(w)}`, 'priya', 'POST',
      '/api/orders',
      { slot: '12:40', items: [{ menuItemId: 1, quantity: w }] });
  }
  await probe('quantity 6', 'priya', 'POST', '/api/orders',
    { slot: '12:40', items: [{ menuItemId: 1, quantity: 6 }] });
  await probe('11 items in all', 'priya', 'POST', '/api/orders',
    { slot: '12:40',
      items: [{ menuItemId: 1, quantity: 5 },
        { menuItemId: 2, quantity: 5 },
        { menuItemId: 4, quantity: 1 }] });
  await probe('same item twice', 'priya', 'POST', '/api/orders',
    { slot: '12:40',
      items: [{ menuItemId: 1, quantity: 1 },
        { menuItemId: 1, quantity: 1 }] });
  await probe('no items', 'priya', 'POST', '/api/orders',
    { slot: '12:40', items: [] });
  await probe('slot 12:35', 'priya', 'POST', '/api/orders',
    { slot: '12:35', items: [{ menuItemId: 1, quantity: 1 }] });
  await probe('id abc', 'priya', 'GET', '/api/orders/abc');
  await probe('id 0', 'priya', 'GET', '/api/orders/0');
  await probe('id 017', 'priya', 'GET', '/api/orders/017');
  await probe('id 4294967296', 'priya', 'GET', '/api/orders/4294967296');
  await probe('date 2026-02-30', 'lata', 'GET',
    '/api/reports/daily?date=2026-02-30');
  await probe('date 29-09-2026', 'lata', 'GET',
    '/api/reports/daily?date=29-09-2026');
  await probe('price 70.5 rupees', 'lata', 'POST', '/api/menu',
    { name: 'Test Item', category: 'snacks', pricePaise: 7050.5,
      isVeg: true });
  await probe('stock 1001', 'lata', 'PUT', '/api/menu/1/stock',
    { stockLeft: 1001 });
  await probe('status flying', 'lata', 'PATCH', '/api/orders/1/status',
    { status: 'flying' });
  await probe('body not JSON', 'priya', 'POST', '/api/orders',
    null, { type: 'text/plain', body: 'slot=12:40' });
  await probe('broken JSON', 'priya', 'POST', '/api/orders',
    null, { type: 'application/json', body: '{"slot":' });
  await probe('body of 11 kB', 'priya', 'POST', '/api/orders',
    null, { type: 'application/json',
      body: JSON.stringify({ pad: 'x'.repeat(11000) }) });
  console.log('probes:', answers.length, ' server errors:',
    answers.filter((a) => a.startsWith('5')).length);
})();
munotes.in327

Validation: Checking Every Input on the Server

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ node ~/sweep.js
quantity null            400 invalid_input
quantity true            400 invalid_input
quantity 0               400 invalid_input
quantity -1              400 invalid_input
quantity 1.5             400 invalid_input
quantity "abc"           400 invalid_input
quantity []              400 invalid_input
quantity {}              400 invalid_input
quantity "xxxxxxxxxxxx..." 400 invalid_input
quantity "' OR 1=1 --"   400 invalid_input
quantity "<script>x</s..." 400 invalid_input
quantity 1e+308          400 invalid_input
quantity 4294967296      400 invalid_input
quantity 6               400 invalid_input
11 items in all          400 invalid_input
same item twice          400 invalid_input
no items                 400 invalid_input
slot 12:35               400 invalid_input
id abc                   404 not_found
id 0                     404 not_found
id 017                   404 not_found
id 4294967296            404 not_found
date 2026-02-30          400 invalid_input
date 29-09-2026          400 invalid_input
price 70.5 rupees        400 invalid_input
stock 1001               400 invalid_input
status flying            400 invalid_input
body not JSON            415 json_only
broken JSON              400 bad_json
body of 11 kB            413 too_large
probes: 30  server errors: 0
$ rm ~/sweep.js

Read the table for what it teaches:

  • Every kind of wrong value is a 400, one refusal with the field named, and never a 500.
  • "abc" for a quantity is refused, not converted. So is 1.5, and so is a number too large for the column.
  • A quantity of 6 and an eleventh item are refused by validation; they are limits that do not depend on the canteen's state.
  • A slot of 12:35 is refused, because the slots are a fixed list, not any time.
  • Bad ids are 404, bad dates are 400, as the two rules above explain.
  • A body that is not JSON, broken JSON, or too large is refused by the pipeline before any route runs (Chapter 42).

A sweep like this belongs in your own project too. Chapter 64 turns it into the input-validation check the syllabus asks for, run against the deployed system and recorded in the report.

The database has the last word

Even if a bug got past every check above, three CHECK constraints and the column types refuse the row (Chapter 29):

munotes.in328

Validation: Checking Every Input on the Server

  CONSTRAINT ck_order_items_quantity
    CHECK (quantity BETWEEN 1 AND 5)

A constraint is not a substitute for validation, because it gives no useful message and it is found only when the row is written. It is the last net, and it is the reason a data error in this system needs two independent mistakes.

Do this for your project

  1. Check every input on the server, whatever your pages check.
  2. Gather every fault and answer once, with a message for each field, keyed by the field's own name.
  3. Check the type first, then the range or length, then membership of the allowed set.
  4. Take the fields you want into a new object; never pass the request's body on.
  5. Answer "not found" for an id that cannot name a row, and parse dates strictly.
  6. Put the numbers in your rules module, not in the validator.
  7. Sweep every endpoint with wrong kinds, empty values, huge values and boundary values, and make sure nothing answers 500.

Mistakes that cost marks

Trusting the page. The one mistake this whole chapter is about.

Blacklists: refusing apostrophes and angle brackets, which breaks real names and stops nothing.

Converting instead of refusing: Number("3") quietly accepting a string, and hiding the bug that sent it.

Passing req.body straight to the database, so an extra field can set anything.

One error at a time, so a form with three faults takes three attempts.

The limits written in the validator, the page and the schema, with three different numbers by the end of term.

Quick revision

  • The page's checks help the user; the server's protect the system; the database's constraints are the last net.
  • Positive validation: type, then range or length, then membership of a list.
  • One refusal, every field: 400 invalid_input with details keyed by the field's name (items.0.quantity).
  • Ids: strict pattern, answered 404. Dates: parsed and compared back, answered 400.
  • Build a new object from accepted fields; never pass the body on.
  • Numbers come from the rules, not from the validator.

Questions you must be able to answer

1. Why validate on the server when the browser already validates? Because a request needs no browser. Anyone can send one directly, and then every rule in the page is simply absent. The page's checks exist to help an honest user; the server's are what protect the data.

2. What is the difference between validation and a business rule? Validation refuses what is wrong whatever the state of the system: a quantity of 7 when the limit is 5. A business rule refuses what is wrong now: an order for an item that has sold out, which tomorrow would be accepted. The first lives in the validator, the second in the services.

munotes.in329

Validation: Checking Every Input on the Server

3. Why report every faulty field at once? Because a form with three mistakes should be corrected once, not three times. The validator gathers the faults into one error keyed by field name, and the page puts each message beside its own field.

4. Why does an id that is not a number answer 404 rather than 400? Because such an id cannot name any row: "there is no such order" is both true and the answer that reveals least, and it is the same answer someone else's real order gets.

5. Why does the validator build a new object instead of passing the body on? So that a field nobody asked for cannot travel with the request into the rest of the application. Only the fields the validator accepted are in the object the service receives.

6. If the database has CHECK constraints, why validate at all? Because a constraint gives the user no useful message, fires only when the row is written, and cannot express rules that depend on more than one row. It is the last net under the validation, not a replacement for it.

Contents This chapter on its own page

munotes.in330

Chapter Forty-Eight

Error Handling

Syllabus topic Module 2, "Application Development: Error handling".

In one line

Error handling is deciding, for everything that can go wrong, whether it was expected or not: an expected refusal carries a status, a code for the program and a sentence for the person, and reaches the page beside the field it concerns; an unexpected failure is logged in full and answered with one plain apology that tells an attacker nothing.

In the wording to use when asked: error handling distinguishes anticipated domain errors, which are represented by a dedicated error type carrying an HTTP status, a machine-readable code and a human-readable message, from unanticipated failures, which are logged with their stack and answered with a generic 500; both are produced by a single centralised handler, so that every response has one shape and no internal detail is disclosed.

Two kinds of thing going wrong

ExpectedUnexpected
Examplesa wrong password, a sold-out item, an order that is not yours, a quantity of 7the database is down, a null where an object was assumed, a typo in a query
Whose faultusually the caller's, sometimes just the state of the canteenours
What the user seesexactly what is wrong, and what to do"Something went wrong on our side. Please try again."
What the log seesone ordinary request linethe whole error, with its stack
What it is called herean AppErroranything else

The difference is decided when the error is created, not when it is caught. A service that knows the biryani has sold out throws an AppError; nothing else in the application throws one. So the handler at the end needs no cleverness: instanceof AppError is the whole question.

The error type, and the makers

'use strict';

// An error we expected and can explain to the user: a wrong
// password, a sold-out item, an order that is not theirs.
// Anything else that is thrown is a bug, and the error
// handler treats it as one (src/middleware/errors.js).
class AppError extends Error {
  constructor(status, code, message, details) {
    super(message);
    this.name = 'AppError';
    this.status = status;
    this.code = code;
    this.details = details;
  }
}

function badRequest(message, details) {
  return new AppError(400, 'invalid_input', message, details);
}

function notSignedIn() {
  return new AppError(401, 'not_signed_in',
    'Please sign in first.');
}

function forbidden() {
  return new AppError(403, 'forbidden',
    'Your account is not allowed to do that.');
}

function notFound(what) {
  return new AppError(404, 'not_found', `${what} not found.`);
}

function conflict(code, message, details) {
  return new AppError(409, code, message, details);
}

function tooManyAttempts(retryAfterSeconds) {
  return new AppError(429, 'too_many_attempts',
    'Too many failed sign-ins. Try again later.',
    { retryAfterSeconds });
}

module.exports = {
  AppError, badRequest, notSignedIn, forbidden, notFound,
  conflict, tooManyAttempts,
};

Each maker builds one kind of refusal, and the names say what they mean rather than what number they are: a route calls notFound('Order'), not new AppError(404, ...). Three things travel with every one:

munotes.in331

Error Handling

  • The status, which the browser and any program can act on;
  • The code, a fixed word the page can test, sold_out, slot_taken, invalid_move, which never changes when the wording does;
  • The message, a plain sentence ready to show; and, where it helps, details: the fault in each field, or how many portions are left.

conflict takes its code because 409 covers several refusals, each of which a page may want to treat differently (Chapter 30).

The one handler

'use strict';

const path = require('node:path');
const { AppError } = require('../errors');

// An address under /api that no route answered.
function apiNotFound(req, res, next) {
  next(new AppError(404, 'not_found',
    'There is nothing at this address.'));
}

// Any other address that no file answered.
function pageNotFound(req, res) {
  res.status(404).sendFile(
    path.join(__dirname, '..', '..', 'public', '404.html'));
}

function send(res, status, code, message, details) {
  res.status(status).json({ error: { code, message, details } });
}

// The last stop for every error. An AppError was expected,
// so its message is shown. Anything else is a bug: the user
// gets a plain apology and the details go to the log, never
// to the browser, where they would help an attacker.
function errorHandler(log) {
  return (err, req, res, next) => {
    if (res.headersSent) return next(err);
    if (err instanceof AppError) {
      if (err.status === 429) {
        res.set('Retry-After', String(err.details.retryAfterSeconds));
      }
      return send(res, err.status, err.code, err.message,
        err.details);
    }
    if (err.type === 'entity.parse.failed') {
      return send(res, 400, 'bad_json',
        'The request body is not valid JSON.');
    }
    if (err.type === 'entity.too.large') {
      return send(res, 413, 'too_large',
        'The request body is too large.');
    }
    log.error(`${req.method} ${req.originalUrl} failed:`, err);
    send(res, 500, 'server_error',
      'Something went wrong on our side. Please try again.');
  };
}

module.exports = { apiNotFound, pageNotFound, errorHandler };

Reading it in order is reading the policy:

  1. If the answer has already begun, hand the error back to Express: nothing else is honest.
  2. An AppError is answered with its own status, code, message and details. A 429 also gets its Retry-After header, the number of seconds from the limiter (Chapter 46).
  3. Express's own parse failures are translated into the application's own shapes, 400 bad_json and 413 too_large, so that the page meets one kind of error and never Express's wording.
  4. Anything else is a bug: the method, the address and the whole error go to the log, and the browser gets one sentence with no code, no message from the error, no file name and no stack (S11).

Two more middlewares complete it: an unknown address under /api becomes a JSON 404, and any other unknown address gets the HTML page (Chapter 40), because a person who mistypes an address should see a page, not JSON.

munotes.in332

Error Handling

The status codes, as this application uses them

Every one of these is produced in this chapter's transcripts.

CodeMeaningHere
200it worked, here is the resultevery successful read and change
201it worked, and something new existsa new order, account or menu item
204it worked, nothing to send backsigning out
400the request itself is wrongvalidation (invalid_input), broken JSON (bad_json)
401who are you?not signed in, or a wrong email or password
403we know, and you may notthe wrong role, or a change from another site
404no such thingan unknown address, an id that cannot name a row, someone else's order
409not now, because of how things areslot_closed, slot_taken, sold_out, item_unavailable, invalid_move, email_taken, name_taken
413, 415too large; not JSONbodies the server refuses to read
429too many triesfive failed sign-ins in fifteen minutes
500our faulta bug: logged in full, apologised for plainly
503not available at the momentthe health check when the database cannot be reached

The line that matters most is 400 against 409. A quantity of 7 is a 400: it is wrong whatever the canteen is doing. An order for a sold-out item is a 409: the request is proper, and tomorrow the same one succeeds. Getting that line right is what lets a page say something useful.

One order route, six answers

The order route can produce most of them. Each of these is one request:

$ cd ~/canteen-preorder
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ order() { curl -s -o /tmp/body -w "%{http_code}" -b "$1" \
>   -H 'Content-Type: application/json' -d "$2" \
>   localhost:3000/api/orders; echo " $(jq -c '.error.code // .order.id' /tmp/body)"; }
$ order none '{"slot":"12:40","items":[{"menuItemId":1,"quantity":1}]}'
401 "not_signed_in"
$ curl -s -c lata -o /dev/null -H 'Content-Type: application/json' \
>   -d '{"email":"owner@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ order lata '{"slot":"12:40","items":[{"menuItemId":1,"quantity":1}]}'
403 "forbidden"
$ curl -s -c priya -o /dev/null -H 'Content-Type: application/json' \
>   -d '{"email":"priya@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ order priya '{"slot":"12:40","items":[{"menuItemId":1,"quantity":7}]}'
400 "invalid_input"
$ order priya '{"slot":"12:40","items":[{"menuItemId":5,"quantity":1}]}'
409 "item_unavailable"
$ order priya '{"slot":"12:40","items":[{"menuItemId":1,"quantity":1}]}'
201 1
$ order priya '{"slot":"12:40","items":[{"menuItemId":1,"quantity":1}]}'
409 "slot_taken"

Six answers, in the order a student might meet them: 401 with nobody signed in; 403 for the owner, who is not a student; 400 invalid_input for a quantity of 7; 409 item_unavailable for an item that is off today's menu; 201 with the order's number; and 409 slot_taken for a second order in the same slot, which is FR-10.

munotes.in333

Error Handling

The last two are the pair to remember: the same request, sent twice, is right the first time and refused the second, because the state of the canteen changed in between. That is what a 409 is for.

What a person is told

A code is for the program; the page shows the message (Chapter 41). Three levels:

  • A field's message, beside the field, for a refusal of input: "Choose 1 to 5."
  • The page's message area, for anything else: "Only 2 Chicken Biryani left."
  • The apology, for a 500: "Something went wrong on our side. Please try again."

And one more, which is easy to forget: no connection at all. api.js turns a failed fetch into an error of the same shape, with status 0 and a message about the network (Chapter 40), because on a phone at 12:15 the commonest failure is the Wi-Fi.

When something really goes wrong

The health check is the one route that answers a failure with a status of its own, because that is its whole job (Chapter 42). Stopping MySQL shows it and an ordinary route meeting the same problem, and shows what a 500 does and does not say:

$ cd ~/canteen-preorder
$ sudo mysqladmin shutdown
$ curl -s -o /dev/null -w "health: %{http_code}\n" localhost:3000/api/health
health: 503
$ curl -s localhost:3000/api/health | jq -c .
{"status":"down","database":"unreachable"}
$ curl -s -o /dev/null -w "menu:   %{http_code}\n" localhost:3000/api/menu
menu:   500
$ curl -s localhost:3000/api/menu | jq -c .
{"error":{"code":"server_error","message":"Something went wrong on our side. Please try again."}}
$ grep -c 'GET /api/menu failed' server.log
2
$ grep -o 'Error: [^,]*' server.log | head -1
Error: connect ECONNREFUSED 127.0.0.1:3306
$ lab-entry true
$ curl -s -o /dev/null -w "health again: %{http_code}\n" localhost:3000/api/health
health again: 200
$ curl -s localhost:3000/api/menu | jq -c '.items | length'
14

Three things to see. The health check answers 503, naming the database, which is what a monitor reads. The menu answers 500 with the apology and nothing else: no code from the driver, no address, no stack, because a page asking for the menu can do nothing with any of it (S11). And the log has the failure in full, with the method, the address and the driver's own error, which is what a developer needs.

Then MySQL is started again, and the next request succeeds: the pool opens new connections by itself, and nothing had to be restarted. A system that needs a restart after its database hiccups is a system that will be restarted during the demonstration.

munotes.in334

Error Handling

Do this for your project

  1. Decide, for every failure, whether it is expected. Throw one kind of error for the expected ones, from the place that knows.
  2. Give each a status, a code for programs and a sentence for people, and details only where they help.
  3. Handle them all in one place, at the end of the pipeline.
  4. Never send a stack, a file name or a database message to a browser; log them instead.
  5. Keep 400 for what is always wrong and 409 for what is wrong now.
  6. Answer an unknown API address with JSON and an unknown page address with a page.
  7. Handle "no connection at all" in the client as carefully as any refusal.

Mistakes that cost marks

Everything is a 500, or worse, everything is a 200 with {"ok": false} inside.

The stack in the browser, which is a map of the server for anyone who asks.

A different error shape from every route, so the pages need a special case each.

catch (err) {}, which turns a bug into wrong behaviour nobody can find.

Codes the pages test by their English, so changing "Sold out" to "Sold out for today" breaks the application.

No message for a lost connection, so a student on bad Wi-Fi sees a page that does nothing at all.

Quick revision

  • Expected (AppError: status, code, message, details) against unexpected (logged, 500, one sentence).
  • Makers: badRequest, notSignedIn, forbidden, notFound, conflict(code, ...), tooManyAttempts.
  • One handler at the end: headersSent, AppError, Express's parse errors translated, everything else logged.
  • 400 = always wrong; 409 = wrong now. 401 = who are you; 403 = you may not; 404 = no such thing.
  • Retry-After with a 429; 503 from the health check; status 0 in the client for no connection.
  • Unknown /api address: JSON 404. Unknown page: the 404 page.

Questions you must be able to answer

1. How does the application tell an expected refusal from a bug? By its type. Anything the code knew could happen is thrown as an AppError carrying a status, a code and a message; nothing else creates one. The handler at the end answers an AppError with its own details and treats everything else as a bug.

2. Why are 400 and 409 both used, and how do you choose? 400 is for a request that is wrong whatever the state of the system, such as a quantity above the limit. 409 is for a request that is proper but cannot be carried out now, such as ordering an item that has sold out or a second order for the same slot. The same 409 request may succeed later.

munotes.in335

Error Handling

3. Why does a 500 tell the user so little? Because the details help an attacker more than the user: a stack trace reveals the framework, the file layout and often the database's table names, and a user can do nothing with any of it. The details go to the log, where the developers can read them.

4. What is the code in an error for, when there is already a message? The message is for the person and may be reworded at any time; the code is a fixed word for the program, so a page can treat sold_out differently from slot_taken without ever testing English text.

5. What happens when the database is down? The health check answers 503, saying the database is unreachable, which is what a monitor watches. Any other route's query fails, the error is logged in full, and the browser is told only that something went wrong. When the database comes back, the pool opens new connections and requests succeed again.

6. Why does an unknown address under /api answer JSON while any other unknown address answers a page? Because the caller of an API address is a program, which needs the same shape of answer as every other refusal, while the caller of any other address is a person in a browser, who should see a readable page with a way back.

Contents This chapter on its own page

munotes.in336

Chapter Forty-Nine

Development Progress and Code Review

Syllabus topic MU's EVALUATION SCHEME, section C: the internal component "Development Progress & Code Review", judged while Module 2 is being built.

In one line

Five of the guide's twenty marks are for work that cannot be produced at the end: a history that shows the project being built week by week, and code the team can explain line by line when the guide reads it with them.

In the wording to use when asked: development progress is evidenced by the version control history, the issue tracker and working increments demonstrated over time; a code review is a systematic examination of source code by someone other than its author, against correctness, clarity, consistency with the design and adherence to the project's conventions, whose findings are recorded and acted on.

What the component asks for

MU prints the name, "Development Progress & Code Review", and 5 marks, and nothing else. Read plainly, it has two halves:

  • Progress, which is judged over time: a guide who sees a repository grow week by week, and something that runs at every meeting, is judging something a student cannot fake in the last week.
  • A code review, which is judged in person: the guide reads your code with you and asks about it.

Both are about evidence that the work is yours. Chapter 1 explains why this paper is built so that a downloaded project fails, and this component is the first of the three places it fails.

Showing progress

Four things a guide can see at any moment, and what each shows:

EvidenceWhat it showsWhere it comes from
Commits spread across the weeks, by every memberthe work was done over the semester, by the teamGit (Chapters 61, 62)
Issues opened and closedthe work was planned and finished, not just startedGitHub Issues (Chapter 56)
Something that runs at every meetingeach increment worked (Chapter 15)the increments
Tests that pass, and growthe work is checked as it is writtenthe test suite (Chapters 50 to 55)

The worked team's Tuesday stand-up, fifteen minutes after the lecture, is the smallest of these and the one that keeps the others true: what each did, what each will do next, what is blocking them. Six of them fall in Module 2, between 15 September and 20 October, and their hours are in the work breakdown structure, because a meeting is work (Chapter 16).

What a guide notices most is the shape of the history. Twenty commits on one night in October says the project was written in one night in October, whatever the code is like.

What a code review is

A review is one person reading another's code and asking about it. It is not a hunt for mistakes to be ashamed of: most of what it finds is a name that misleads, a comment that is now false, a case nobody thought of. Finding those is cheap now and expensive after the code is built upon.

munotes.in337

Development Progress and Code Review

A team reviews its own work as it goes, which is what a pull request is for (Chapter 62): every change is read by someone who did not write it before it joins the main branch. The guide's review is the same thing from outside, and in the worked project it happened on Thursday 1 October, between the second increment and the testing.

A checklist to review against

Use the same list every time, so that nothing depends on what the reviewer happens to notice:

Question
Correctdoes it do what the requirement says, including the cases nobody likes: nothing, too much, the wrong type, the same request twice?
Testedis there a test that fails if this code is wrong, and does the suite still pass?
In its layeris this the right file for this decision, and does it use only what its layer may use (Chapter 28)?
Clearwould someone who has not seen it guess what it does from its name, and is every name the same word the rest of the system uses?
Safeis every value validated, every SQL value a placeholder, every piece of text added to a page as text, and nothing secret logged?
Smallis this change one thing, or several things that should be separate?
Honestdo the comments say why, and is every one of them still true?

Reviewing against the document, mechanically

Chapter 36's architecture document made one rule for the backend: each layer may use only the layers below it. A rule like that is worth checking by machine rather than by eye, because it is exactly the sort of thing that drifts one require at a time. The worked team wrote a short script, which reads every file's own require lines and reports what the code does, not what anybody remembers:

'use strict';

// npm run layers
// Checks the one rule the architecture document gives the
// backend (section 5.1): each layer may use only the layers
// below it. It reads every file's own require() lines, so it
// reports what the code does, not what anybody remembers.
//
// The one allowed exception is written down here, as it is in
// the document: the health check asks the database pool
// directly, because testing that connection is its whole job.

const fs = require('node:fs');
const path = require('node:path');

const SRC = path.join(__dirname, '..', 'src');

// Which layer each file belongs to, by where it lives.
const LAYER_OF = [
  ['routes/', 'routes'],
  ['middleware/', 'middleware'],
  ['services/', 'services'],
  ['store/', 'store'],
  ['validate.js', 'validation'],
  ['rules.js', 'rules'],
  ['db.js', 'db'],
  ['errors.js', 'errors'],
  ['passwords.js', 'shared'],
  ['attempts.js', 'shared'],
  ['config.js', 'shared'],
  ['app.js', 'wiring'],
  ['server.js', 'wiring'],
];

// What each layer may require. `errors` and `shared` are
// small leaves every layer may use.
const MAY_USE = {
  wiring: ['routes', 'middleware', 'services', 'store', 'rules',
    'validation', 'db', 'errors', 'shared', 'express'],
  routes: ['services', 'validation', 'middleware', 'errors',
    'shared', 'express'],
  middleware: ['services', 'errors', 'shared', 'express'],
  validation: ['rules', 'errors'],
  services: ['store', 'rules', 'db', 'errors', 'shared'],
  rules: [],
  store: ['errors'],
  db: ['shared', 'mysql2/promise'],
  errors: [],
  shared: ['errors'],
};

// The exception the architecture document records.
const EXCEPTIONS = [
  ['routes/health.js', 'db',
    'the health check asks the pool whether the database answers'],
];

// What must not appear in a layer at all, whatever it
// requires: a service that touches a request can never be
// tested without a server, and a rule that reads the clock
// cannot be tested at any other time of day. SQL is looked
// for as a statement, not as a word: `update` and `insert`
// are ordinary method names.
const SQL = [/SELECT\s+[^;]*\bFROM\b/i, /INSERT\s+INTO\b/i,
  /UPDATE\s+\w+\s+SET\b/i, /DELETE\s+FROM\b/i];
const FORBIDDEN = {
  services: [/\breq\b/, /\bres\b/, ...SQL],
  rules: [/\breq\b/, /\bres\b/, /new Date\(\)/, ...SQL],
  store: [/\breq\b/, /\bres\b/],
  validation: [/\breq\b/, /\bres\b/, ...SQL],
};

function walk(dir) {
  return fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
    const full = path.join(dir, e.name);
    return e.isDirectory() ? walk(full)
      : (e.name.endsWith('.js') ? [full] : []);
  });
}

function layerOf(relative) {
  const found = LAYER_OF.find(([start]) => relative.startsWith(start));
  return found ? found[1] : 'unknown';
}

// The layer a require() names, or null for a node: builtin.
function required(spec, fromRelative) {
  if (spec.startsWith('node:')) return null;
  if (!spec.startsWith('.')) return spec; // express, mysql2
  const target = path.normalize(
    path.join(path.dirname(fromRelative), spec));
  // A require names a file or a folder: './errors' is
  // errors.js, './store' is store/index.js.
  const tried = [target, `${target}.js`, `${target}/`];
  const found = tried.map(layerOf).find((l) => l !== 'unknown');
  return found || 'unknown';
}

// Everything wrong with one file's text.
function faults(relative, layer, text) {
  const found = [];
  const allowed = MAY_USE[layer] || [];
  for (const m of text.matchAll(/require\('([^']+)'\)/g)) {
    const used = required(m[1], relative);
    if (used === null || used === layer) continue;
    const excused = EXCEPTIONS.some(
      ([file, target]) => file === relative && target === used);
    if (excused || allowed.includes(used)) continue;
    found.push(`${relative} (${layer}) requires ${used}`);
  }
  // Comments explain the rules; only real code breaks them.
  const code = text.replace(/\/\/[^\n]*/g, '');
  for (const bad of FORBIDDEN[layer] || []) {
    if (bad.test(code)) {
      found.push(`${relative} (${layer}) contains ${bad}`);
    }
  }
  return found;
}

function check() {
  const problems = [];
  let files = 0;
  let lines = 0;
  for (const full of walk(SRC)) {
    const relative = path.relative(SRC, full);
    const text = fs.readFileSync(full, 'utf8');
    files += 1;
    lines += text.split('\n').length;
    problems.push(...faults(relative, layerOf(relative), text));
  }
  return { problems, files, lines };
}

// A check that has never failed has not been tested. Five
// breaches are planted in COPIES of the text (nothing on disk
// is touched) and every one must be reported.
function selfTest() {
  const planted = [
    ['routes/orders.js', 'routes', "const s = require('../store');"],
    ['services/menu.js', 'services',
      "const q = 'SELECT name FROM menu_items';"],
    ['rules.js', 'rules', 'const now = new Date();'],
    ['services/orders.js', 'services',
      'function handler(req, res) { return res; }'],
    ['store/menu.js', 'store', "const e = require('express');"],
  ];
  for (const [file, layer, line] of planted) {
    const found = faults(file, layer, `'use strict';\n${line}\n`);
    if (found.length === 0) {
      throw new Error(`check-layers misses: ${line}`);
    }
  }
}

selfTest();
const { problems, files, lines } = check();
for (const p of problems) console.error('LAYER: ' + p);
if (problems.length > 0) {
  console.error(`${problems.length} break(s) of the layer rule.`);
  process.exitCode = 1;
} else {
  console.info(`layers: ${files} files, ${lines} lines, every `
    + 'require() allowed by the architecture document, with its '
    + 'one recorded exception.');
}
munotes.in338

Development Progress and Code Review

Two things about it are worth copying. It carries the one exception the architecture document records, the health check asking the pool directly, so the exception is in the code as well as the prose. And it plants five breaches of the rule in copies of the text on every run, and refuses to report anything if it misses one: a check that has never failed has not been tested.

munotes.in339

Development Progress and Code Review

$ cd ~/canteen-preorder
$ npm run layers

> canteen-preorder@1.0.0 layers
> node scripts/check-layers.js

layers: 27 files, 1698 lines, every require() allowed by the architecture document, with its one recorded exception.

The worked review

Thursday 1 October, forty minutes in the lab, Prof. Iyer with all four members and the code on the screen.

Before it, the team did three things, and a team that does them turns a review into a conversation about design rather than a hunt for obvious faults:

  • ran the whole suite and the layer check, so nothing failing was on the screen;
  • closed the issues that were done, so the board showed the truth;
  • wrote down the two questions they wanted answered.

What the guide looked at, and what he asked:

WhatThe questionThe answer
The history"Show me the last two weeks."commits by all four, spread across the days, each naming what it changed
One pull request"Who reviewed this, and what did they say?"Rohan's review of Farhan's ordering change: three comments, two taken
services/orders.js"What happens if the second item is short?"the whole transaction is rolled back, and the test that proves it
validate.js"Why is "3" refused for a quantity?"because converting it would hide the bug in whatever sent it
passwords.js"Why scrypt and not Argon2id?"Node.js 22 has no Argon2, and NFR-12 includes it (ADR-1)
counter.js"Where does Mark ready come from?"the state machine's own trigger, and the rules' MOVES
munotes.in340

Development Progress and Code Review

The action from the design review, shown. Chapter 37 recorded that Farhan, with Rohan, would show the concurrency test passing at this review. They ran it:

$ cd ~/canteen-preorder
$ node --test --test-reporter=./test/reporter.js \
>   test/integration/concurrency.test.js
test/integration/concurrency.test.js
  pass  sells the last 5 plates to exactly 5 of 20 students
  pass  accepts only one of two orders one student sends at once

2 tests: 2 passed, 0 failed

Twenty students, five plates, exactly five accepted: NFR-2, which no amount of clicking could have shown.

What the review found. Three things, none of them a disaster, which is what a healthy review looks like:

  1. A comment that had become false. mock.js said it answered "every request the pages make"; it answers the student's six (Chapter 40). Corrected the same day.
  2. A message with numbers in it. A sold-out refusal read "Only 2 Chicken Biryani left. 3 2", because the page printed every detail of the error, including the item's id and the stock. Found by Aditi while showing the counter's screen. It became an issue, and Chapter 56 follows it from report to fix.
  3. A name that misled. move in the order service does both cancelling and the counter's changes; the guide read it as "move to the next status". It kept its name, with a comment saying what it covers, and both callers were listed above it.

What the guide said about the history, which is the part worth repeating: he could see, without asking, that the frontend and the backend had been built side by side from 11 September, because the commits alternate between public/ and src/ through those two weeks.

Do this for your project

  1. Commit on the day you write the code, with a message that says what changed and why.
  2. Open an issue for every piece of work, and close it when it is done.
  3. Have every change read by someone who did not write it, before it joins the main branch.
  4. Review against a checklist, not against your memory.
  5. Make the rules of your design checkable by a script where you can, and run it before every review.
  6. Before the guide's review: run everything, close what is done, and bring your own questions.
  7. Write down what the review found, and turn each finding into an issue.

Mistakes that cost marks

One commit, on the last night, called "project".

A repository where only one member ever committed, in a project of four.

munotes.in341

Development Progress and Code Review

Reviewing by "looks fine to me", which finds nothing and teaches nobody.

Defending the code in a review instead of listening: the reviewer is the first reader, and if they misread it, so will the examiner.

Findings that go nowhere, because nobody wrote them down.

A design rule nothing checks, which drifts one require at a time until the layers are a memory.

Quick revision

  • The component: Development Progress & Code Review, 5 marks, the guide's; MU prints no criteria.
  • Progress is shown over time: commits by everyone across the weeks, issues closed, something running at every meeting, tests that grow.
  • A code review: someone else reads your code and asks about it; a checklist every time.
  • Make design rules checkable: the layer rule is a script that also plants breaches to prove it works.
  • Before the guide's review: everything green, the board true, your own questions ready.
  • Every finding becomes an issue.

Questions you must be able to answer

1. What does the "Development Progress" half of this component reward, and how is it shown? Work done over the semester rather than at the end, shown by a version control history with commits from every member spread across the weeks, issues opened and closed, tests that grow with the code, and something that runs at every meeting with the guide.

2. What is a code review, and what should it look for? One or more people other than the author reading the code and asking about it, against a fixed checklist: is it correct, including the awkward cases; is it tested; is it in the right layer; is it clear and consistently named; is it safe; is it one change; and are its comments true.

3. How can a design rule be enforced rather than merely written down? By making it checkable. The layer rule, that each layer may use only the layers below it, is checked by a script that reads every require in the source and reports any that the architecture document does not allow, with its one recorded exception.

4. Why does that script plant faults in its own input? Because a check that has never failed has not been tested: if a mistake in the script made it report nothing, a passing run would look exactly the same as a correct system. Planting breaches it must catch proves it can still fail.

5. What should a team do before the guide's code review? Run the whole test suite and any design checks, so nothing failing is on the screen; close the issues that are finished, so the board shows the truth; and prepare the questions they want the guide's opinion on.

munotes.in342

Development Progress and Code Review

6. The review found a comment that was no longer true. Why does that matter as much as a bug? Because the next person to read the code will believe it, and act on it. A false comment is a bug that has been written down and signed.

Contents This chapter on its own page

munotes.in343

Chapter Fifty

Testing the Project: the Levels, the Test Plan and the Tools

Syllabus topic Module 2, "Integration & System Testing", as a whole: the levels of testing, the plan, and the tools.

In one line

Testing is not one activity but four, each answering a different question, unit, integration, system and acceptance, written down in advance in a test plan that says what will be tested, how and by whom, and run in this project by one command with no testing library at all.

In the wording to use when asked: software testing is conducted at successive levels: unit testing of individual components in isolation, integration testing of components working together, system testing of the complete system against its specification, and acceptance testing by or on behalf of the user; test design may be black-box, derived from the specification, or white-box, derived from the code's structure, and the test plan records the scope, approach, resources, schedule and the criteria for passing.

The four levels

LevelThe question it answersHereWho
Unitdoes this one piece do what it should, on its own?the rules, the validation, the password functions, the limiterthe person who wrote it
Integrationdo the pieces work together, with the real database?the routes, services and store, through real HTTP requeststhe team
Systemdoes the whole thing do what the SRS says?every requirement, end to end, on the deployed systemthe team, from the SRS
Acceptancewill the people who asked for it use it?the owner and Ganesh, at the canteenthe users

Each level catches what the one below it cannot. A unit test proves canMove('placed', 'ready', 'staff') is false; it cannot prove that the counter's button sends the right status. An integration test proves the route refuses that move; it cannot prove that the counter can see the button during the rush. That is what system and acceptance testing are for (Chapter 54).

The cost of a mistake rises at every level. A unit test fails in a second on a laptop; a fault found in acceptance testing costs a meeting, a change, and another round of everything.

Black-box and white-box

Two ways of deciding what to test, and a project needs both:

  • Black box: tests derived from the specification alone, without looking at the code. "FR-7 allows 1 to 5 of an item" gives tests at 0, 1, 5 and 6, whatever the code looks like. Chapter 52 is the systematic way of doing this.
  • White box: tests derived from the code's own structure, to reach paths the specification does not name: what happens when the second item of an order is short, when the database is down, when the same request arrives twice.

A suite of only black-box tests misses the paths the code invented for itself; a suite of only white-box tests proves the code does what it does, which is not the same as what it should.

munotes.in344

Testing the Project: the Levels, the Test Plan and the Tools

The tools

Node.js has a test runner built into it, node:test, and an assertion library, node:assert. The worked project uses them and nothing else: no Jest, no Mocha, no Chai, no supertest. ADR-1 chose the stack for what the team could explain; a test suite is the last place to add something nobody can (Chapter 26).

A test file reads the same as it would with any of those libraries:

describe('isSlotOpen', () => {
  it('is open one minute before the cut-off', () => {
    assert.equal(rules.isSlotOpen('12:40', at('12:24'), IST), true);
  });
  • describe groups tests; it is one test, named so that the report reads as a sentence.
  • assert.equal compares with ===; assert.deepEqual compares objects and arrays; assert.match tests a regular expression; assert.ok takes anything true.
  • The strict form, node:assert/strict, is what the project imports, so assert.equal(1, '1') fails, as it should.

One command

    "test": "node --test --test-concurrency=1 --test-reporter=./test/reporter.js \"test/**/*.test.js\"",
    "test:unit": "node --test --test-reporter=./test/reporter.js test/unit/*.test.js",

Three details, each of which was a mistake first:

  • The pattern is named. Without test/**/*.test.js, node --test walks the whole project and runs anything that looks like a test, which once meant the load-testing script, which then "failed" for want of a running server.
  • --test-concurrency=1 runs one file at a time, because the integration tests share one database and rebuild it for each file. Tests that fight over one database fail at random, which is worse than failing.
  • A reporter of the project's own. Node's default marks each test with a tick or a cross, which not every terminal can draw; this one prints plain words, so the report is readable anywhere, including in this book.
'use strict';

const path = require('node:path');

// The report `npm test` prints. Node's own reporter marks
// each test with a tick or a cross, which some terminals
// (older Windows ones among them) cannot draw; this one
// prints plain words: each file, each test in it, then the
// totals. A failure also prints why it failed.

module.exports = async function* plainReporter(source) {
  let file = '';
  let passed = 0;
  let failed = 0;
  for await (const { type, data } of source) {
    if (type === 'test:stderr') {
      yield data.message;
      continue;
    }
    if (type !== 'test:pass' && type !== 'test:fail') continue;
    if (data.details.type === 'suite') continue;
    if (data.file && data.file !== file) {
      file = data.file;
      yield `${path.relative(process.cwd(), file)}\n`;
    }
    if (type === 'test:pass') {
      passed += 1;
      yield `  pass  ${data.name}\n`;
    } else {
      failed += 1;
      const err = data.details.error;
      const why = (err.cause && err.cause.message) || err.message;
      yield `  FAIL  ${data.name}\n        ${why}\n`;
    }
  }
  yield `\n${passed + failed} tests: ${passed} passed, `
    + `${failed} failed\n`;
};
munotes.in345

Testing the Project: the Levels, the Test Plan and the Tools

npm test runs everything; npm run test:unit runs only the fast tests, in a second, which is what a developer runs every few minutes while writing code (NFR-11).

The shared helper

Every integration test needs the same three things: a database in a known state, the application running, and a client that keeps its cookie as a browser does. One helper gives all three:

'use strict';

// Shared by the integration tests: a freshly built test
// database, the application started on a free port, and a
// client that keeps its sign-in cookie between requests the
// way a browser does.

const fs = require('node:fs');
const path = require('node:path');
const { loadConfig } = require('../src/config');
const { createPool } = require('../src/db');
const { createStore } = require('../src/store');
const { createApp } = require('../src/app');
const { resetDatabase } = require('../scripts/setup-db');

const envFile = path.join(__dirname, '..', '.env');
if (fs.existsSync(envFile)) process.loadEnvFile(envFile);

// Tests never touch the real database: they use their own.
// A test may change other settings, as the HTTPS test does.
function testConfig(settings = {}) {
  return loadConfig({
    ...process.env,
    DB_NAME: process.env.TEST_DB_NAME || 'canteen_test',
    LOG_REQUESTS: 'false',
    ...settings,
  });
}

// A clock the tests can set. Unless a test moves it, it is
// 10:30 in the morning in Mumbai, when every slot is open.
function testClock(when = '2026-09-29T10:30:00+05:30') {
  let now = new Date(when);
  const clock = () => now;
  clock.set = (value) => {
    now = new Date(value);
  };
  return clock;
}

// Keeps the test output clean, but never hides a real error.
const quietLog = { info() {}, error: console.error };

async function startApp(settings) {
  const config = testConfig(settings);
  await resetDatabase(config.db);
  const pool = createPool(config.db);
  const clock = testClock();
  const app = createApp({
    config, pool, store: createStore(pool), clock, log: quietLog,
  });
  const server = await new Promise((resolve) => {
    const s = app.listen(0, '127.0.0.1', () => resolve(s));
  });
  const base = `http://127.0.0.1:${server.address().port}`;
  return {
    base,
    clock,
    pool,
    client: () => client(base),
    async stop() {
      await new Promise((resolve) => server.close(resolve));
      await pool.end();
    },
  };
}

function client(base) {
  let cookie = '';

  async function request(method, url, body, headers = {}) {
    const sent = { ...headers };
    if (cookie) sent.Cookie = cookie;
    if (method !== 'GET') sent['Content-Type'] ??= 'application/json';
    const res = await fetch(base + url, {
      method,
      headers: sent,
      body: method === 'GET' ? undefined : JSON.stringify(body ?? {}),
    });
    const setCookie = res.headers.get('set-cookie');
    if (setCookie) cookie = setCookie.split(';')[0];
    const text = await res.text();
    const json = text && res.headers.get('content-type')
      ?.startsWith('application/json') ? JSON.parse(text) : null;
    return {
      status: res.status, headers: res.headers, body: json, text,
    };
  }

  const c = {
    get: (url, headers) => request('GET', url, undefined, headers),
    post: (url, body, headers) => request('POST', url, body, headers),
    patch: (url, body) => request('PATCH', url, body),
    put: (url, body) => request('PUT', url, body),
    async signIn(email, password = 'canteen-demo') {
      const res = await c.post('/api/auth/login', { email, password });
      if (res.status !== 200) {
        throw new Error(`sign-in as ${email} gave ${res.status}`);
      }
      return res.body.user;
    },
  };
  return c;
}

module.exports = { startApp, testClock };
munotes.in346

Testing the Project: the Levels, the Test Plan and the Tools

  • startApp() rebuilds the test database from the schema and the seed data, then builds the application with that database, a clock that stands still at 10:30 on 29 September 2026, and a quiet log. Every test file therefore starts from exactly the same data, and the tests can be run in any order.
  • The clock can be moved: app.clock.set(...) lets a test see what happens after a cut-off, or eight hours later, without waiting (Chapter 43).
  • client() keeps the set-cookie it receives and sends it back, so a test signs in once and then acts as that person. signIn(email) is the shorthand every test uses.
  • app.base is the address the server is listening on, chosen by the operating system with listen(0), so tests never collide over a port.

The tests never touch the real database. TEST_DB_NAME is a separate one, created in Chapter 44, and startApp empties and rebuilds it every time.

The test plan

A test plan is written before the testing starts, and says six things. MU prints no format, so this is the book's:

SectionWhat it saysThe worked project's
Scopewhat will be tested, and what will notevery functional and non-functional requirement; not the college network or MySQL itself
Approachthe levels and how each is derivedunit and integration written with the code; system from the SRS; acceptance with the owner and the counter
Environmentwhere each level runsunit and integration on the members' laptops and in the lab image; system and acceptance on the lab desktop
Whowho writes and runs each levelRohan owns the tests (WBS 5.1 to 5.4); Aditi owns system and acceptance (5.3)
Schedulewhenunit and integration with the code, to 5 Oct; integration and system 6 to 12 Oct; load and security 13 to 15 Oct
Pass criteriawhen testing is finishedevery test passes; every requirement has at least one test; no open defect of high severity

The last row is the one students leave out, and it is the one that makes a plan a plan. "Every requirement has at least one test" is checkable: it is the last column of the traceability matrix (Chapter 55).

What the suite is

$ cd ~/canteen-preorder
$ npm test 2>&1 | tail -3
  pass  refuses 29-09-2026

117 tests: 117 passed, 0 failed
$ for d in unit blackbox integration; do \
>   printf '%-12s ' "$d"; \
>   node --test --test-concurrency=1 \
>     --test-reporter=./test/reporter.js "test/$d/*.test.js" \
>     2>&1 | tail -1; done
unit         43 tests: 43 passed, 0 failed
blackbox     29 tests: 29 passed, 0 failed
integration  45 tests: 45 passed, 0 failed
$ ls test/unit test/blackbox test/integration
test/blackbox:
order-acceptance.test.js  order-input.test.js

test/integration:
auth.test.js	     counter.test.js  orders.test.js
concurrency.test.js  menu.test.js     security.test.js

test/unit:
attempts.test.js  passwords.test.js  rules.test.js  validate.test.js
munotes.in347

Testing the Project: the Levels, the Test Plan and the Tools

A hundred and seventeen tests in three folders: 43 unit tests of the pure functions and the small modules, 29 black-box tests derived from the requirements, and 45 integration tests that run against the real database. The unit and black-box tests need no database at all, which is why they finish in a moment. Chapters 51 to 53 take them one folder at a time, Chapter 54 adds system and acceptance testing, and Chapter 55 writes the test cases and the report.

What testing does not prove

Testing shows the presence of faults, not their absence: a suite that passes says only that these tests did not find anything. That is worth saying plainly in a report, and it is why the levels matter. The worked project's suite passed every day of the second increment, and system testing still found a message that read "Only 2 Chicken Biryani left. 3 2" (Chapters 49 and 56), because no test had asked what a person would see.

Do this for your project

  1. Plan the four levels, and say who writes and runs each.
  2. Use the test runner your language already has before adding a library.
  3. Write unit tests with the code, not after it.
  4. Give the integration tests their own database, rebuilt for every file.
  5. Make the clock something a test can set.
  6. Run everything with one command, and have a faster command for the unit tests alone.
  7. Write the pass criteria down, and make "every requirement has a test" one of them.

Mistakes that cost marks

Testing in the last week, when a failure can only be hidden.

Tests against the real database, which empty it or, worse, leave rubbish in it before the demonstration.

Tests that pass only in the order they were written, because each leaves data for the next.

A test that waits for real time to pass, so the suite takes minutes and nobody runs it.

"Tested by using the application", with nothing anyone else can run.

A plan with no pass criteria, so testing stops when the deadline arrives.

Quick revision

  • Levels: unit (one piece), integration (pieces together, real database), system (the whole against the SRS), acceptance (the users).
  • Black box from the specification; white box from the code. A project needs both.
  • Tools here: node:test and node:assert/strict, no library; a plain-words reporter.
  • One command (npm test), the pattern named, one file at a time, a separate test database rebuilt each time, a clock a test can set.
  • The plan: scope, approach, environment, who, schedule, pass criteria.
  • Testing shows the presence of faults, not their absence.
munotes.in348

Testing the Project: the Levels, the Test Plan and the Tools

Questions you must be able to answer

1. Name the four levels of testing and what each answers. Unit: does this one piece work on its own? Integration: do the pieces work together, with the real database? System: does the whole system do what the specification says? Acceptance: will the people who asked for it accept and use it?

2. What is the difference between black-box and white-box testing? Black-box tests are derived from the specification without looking at the code, so they test what the system should do. White-box tests are derived from the code's own structure, to reach paths the specification never mentions. A good suite has both.

3. Why do the integration tests use a separate database, rebuilt for every file? So that they never touch real data, and so that every file starts from exactly the same rows. Tests that depend on what an earlier test left behind pass in one order and fail in another.

4. Why does the test command name a pattern instead of letting the runner find the tests? Because a runner that walks the whole project will run anything that looks like a test, including scripts that are not tests: here the load-testing script, which reported a failure for want of a running server.

5. Why is the clock passed into the application rather than read? So that a test can set it: what happens a minute before a cut-off, a minute after, or eight hours later, can then be tested in milliseconds instead of waited for.

6. What should the pass criteria of a test plan say? When testing is finished: that every test passes, that every requirement has at least one test that covers it, and that no defect above an agreed severity is still open.

Contents This chapter on its own page

munotes.in349

Chapter Fifty-One

Unit Testing

Syllabus topic Module 2, "Integration & System Testing: Unit testing".

In one line

A unit test calls one function with values it chooses and checks the answer, with no server, no database and no waiting, which is why the parts of a system worth getting exactly right, its rules, its limits and its cryptography, are the parts written as plain functions.

In the wording to use when asked: unit testing exercises the smallest independently testable parts of a system in isolation from their collaborators, using arranged inputs and asserted outputs; it is fast, deterministic and written by the developer alongside the code, and its feasibility is a direct consequence of the design, since pure functions and injected dependencies can be tested where code that reaches for a database or a clock cannot.

What a unit is here

A unit is the smallest thing worth testing on its own: one function, or one small module. The worked project has four files of them, and what is in each says something about the design:

FileWhat it testsWhy it can be tested at all
rules.test.jsthe slots, the cut-offs, the totals, the moves, the clockthe rules are pure functions (Chapter 43)
validate.test.jsevery input rulevalidation depends on nothing but its argument (Chapter 47)
passwords.test.jshashing and verifyingthe password module takes a password and gives a string
attempts.test.jsthe limit on failed sign-insthe limiter is given its clock (Chapter 46)

Every one of those is a decision the system must get right, and none of them needs a database. That is not luck: it is what the layer rule bought (Chapter 28). Code that reaches for the database, the request or the clock can only be tested at the next level up.

Arrange, act, assert

Every test has the same three parts, and naming them is the easiest way to keep tests readable:

  it('allows four failures and blocks at the fifth', () => {
    const clock = fakeClock();
    const limiter = attemptLimiter({ max: 5, windowMs: 15 * MINUTE,
      clock });
    for (let i = 0; i < 4; i += 1) limiter.fail('k');
    assert.equal(limiter.blockedFor('k'), 0);
    limiter.fail('k');
    assert.equal(limiter.blockedFor('k'), 15 * 60);

Arrange: a limiter with a clock of the test's own. Act: four failures, then a fifth. Assert: nothing blocked after four, blocked for 900 seconds after five. That is NFR-6, in eight lines, and it runs in less than a millisecond.

The name of a test is part of the test. Read the report of a good suite and it is a specification: "allows four failures and blocks at the fifth", "is closed at the cut-off itself", "salts: one password never gives the same hash twice".

Testing what depends on time

Two of these modules depend on time, and neither waits for it.

munotes.in350

Unit Testing

The rules are given the moment, so a test simply passes the moment it wants:

'use strict';

const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const rules = require('../../src/rules');

const IST = 'Asia/Kolkata';
const at = (hhmm) => new Date(`2026-09-29T${hhmm}:00+05:30`);

describe('localTime', () => {
  it('reads the canteen clock, not the server clock', () => {
    // 04:50 in London (UTC) is 10:20 in Mumbai.
    const now = new Date('2026-09-29T04:50:00Z');
    assert.deepEqual(rules.localTime(now, IST),
      { date: '2026-09-29', minutes: 620 });
  });

  it('gives the Mumbai date just after midnight there', () => {
    // 18:40 on the 28th in UTC is already the 29th in Mumbai.
    const now = new Date('2026-09-28T18:40:00Z');
    assert.equal(rules.localTime(now, IST).date, '2026-09-29');
  });
});

describe('stampAt', () => {
  it('writes a moment as the canteen clock shows it', () => {
    const now = new Date('2026-09-29T04:50:07Z');
    assert.equal(rules.stampAt(now, IST), '2026-09-29 10:20:07');
  });
});

describe('slotsAt', () => {
  it('has every slot open at 10:30', () => {
    const open = rules.slotsAt(at('10:30'), IST).map((s) => s.open);
    assert.deepEqual(open, [true, true, true, true]);
  });

  it('closes each slot 15 minutes before it', () => {
    assert.deepEqual(rules.slotsAt(at('12:20'), IST), [
      { time: '12:30', cutoff: '12:15', open: false },
      { time: '12:40', cutoff: '12:25', open: true },
      { time: '12:50', cutoff: '12:35', open: true },
      { time: '13:00', cutoff: '12:45', open: true },
    ]);
  });
});

describe('isSlotOpen', () => {
  it('is open one minute before the cut-off', () => {
    assert.equal(rules.isSlotOpen('12:40', at('12:24'), IST), true);
  });

  it('is closed at the cut-off itself', () => {
    assert.equal(rules.isSlotOpen('12:40', at('12:25'), IST), false);
  });

  it('is never open for a slot that does not exist', () => {
    assert.equal(rules.isSlotOpen('12:35', at('09:00'), IST), false);
  });
});

describe('orderTotal', () => {
  it('adds quantity times price, in paise', () => {
    const lines = [
      { quantity: 2, pricePaise: 7000 },
      { quantity: 1, pricePaise: 2000 },
    ];
    assert.equal(rules.orderTotal(lines), 16000);
  });
});

describe('canMove', () => {
  it('lets the counter move an order forward', () => {
    assert.equal(rules.canMove('placed', 'preparing', 'staff'), true);
    assert.equal(rules.canMove('preparing', 'ready', 'owner'), true);
    assert.equal(rules.canMove('ready', 'collected', 'staff'), true);
    assert.equal(rules.canMove('ready', 'no_show', 'staff'), true);
  });

  it('lets only the student cancel, and only while placed', () => {
    assert.equal(rules.canMove('placed', 'cancelled', 'student'), true);
    assert.equal(rules.canMove('placed', 'cancelled', 'staff'), false);
    assert.equal(rules.canMove('preparing', 'cancelled', 'student'),
      false);
  });

  it('refuses a skipped step and any move backwards', () => {
    assert.equal(rules.canMove('placed', 'ready', 'staff'), false);
    assert.equal(rules.canMove('ready', 'preparing', 'staff'), false);
    assert.equal(rules.canMove('collected', 'ready', 'owner'), false);
  });

  it('never lets a student move an order forward', () => {
    assert.equal(rules.canMove('placed', 'preparing', 'student'),
      false);
  });
});

describe('returnsStock', () => {
  it('returns stock on a cancellation only', () => {
    assert.equal(rules.returnsStock('cancelled'), true);
    assert.equal(rules.returnsStock('no_show'), false);
    assert.equal(rules.returnsStock('collected'), false);
  });
});
  • at('12:24') builds a moment in the canteen's own time zone, so every test reads as the time the canteen sees.
  • The time zone is tested, not assumed: 04:50 in UTC is 10:20 in Mumbai, and 18:40 on the 28th in UTC is already the 29th there. Those two tests are what stop the whole system slipping a day for a server in another country (ADR-4).
  • The boundary is tested from both sides: open at 12:24, closed at 12:25, which is the cut-off itself. Chapter 52 explains why the two values either side of a boundary are the ones that find faults.
munotes.in351

Unit Testing

The limiter cannot be given a moment, because it remembers; so it is given a clock it can be moved, four lines at the top of its test file:

// A clock that only moves when the test moves it.
function fakeClock() {
  let now = 1_000_000;
  const clock = () => now;
  clock.advance = (ms) => {
    now += ms;
  };
  return clock;
}

Fifteen minutes pass in the test by calling clock.advance(15 * MINUTE). A test that instead waited would take fifteen minutes, and nobody would run it.

'use strict';

const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { attemptLimiter } = require('../../src/attempts');

// A clock that only moves when the test moves it.
function fakeClock() {
  let now = 1_000_000;
  const clock = () => now;
  clock.advance = (ms) => {
    now += ms;
  };
  return clock;
}

const MINUTE = 60 * 1000;

describe('attemptLimiter', () => {
  it('allows four failures and blocks at the fifth', () => {
    const clock = fakeClock();
    const limiter = attemptLimiter({ max: 5, windowMs: 15 * MINUTE,
      clock });
    for (let i = 0; i < 4; i += 1) limiter.fail('k');
    assert.equal(limiter.blockedFor('k'), 0);
    limiter.fail('k');
    assert.equal(limiter.blockedFor('k'), 15 * 60);
  });

  it('lets the key try again once the window has passed', () => {
    const clock = fakeClock();
    const limiter = attemptLimiter({ max: 5, windowMs: 15 * MINUTE,
      clock });
    for (let i = 0; i < 5; i += 1) limiter.fail('k');
    clock.advance(15 * MINUTE);
    assert.equal(limiter.blockedFor('k'), 0);
  });

  it('forgets the failures after a successful sign-in', () => {
    const clock = fakeClock();
    const limiter = attemptLimiter({ max: 5, windowMs: 15 * MINUTE,
      clock });
    for (let i = 0; i < 5; i += 1) limiter.fail('k');
    limiter.reset('k');
    assert.equal(limiter.blockedFor('k'), 0);
  });

  it('counts each key on its own', () => {
    const clock = fakeClock();
    const limiter = attemptLimiter({ max: 5, windowMs: 15 * MINUTE,
      clock });
    for (let i = 0; i < 5; i += 1) limiter.fail('a');
    assert.equal(limiter.blockedFor('b'), 0);
  });
});

Testing the validation

'use strict';

const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const validate = require('../../src/validate');

// Runs fn and returns the field details of the error it
// throws, so a test can say exactly which fields failed.
function fieldsOf(fn) {
  try {
    fn();
  } catch (err) {
    return err.details;
  }
  assert.fail('expected the input to be refused');
}

describe('validate.account', () => {
  it('trims the name and lower-cases the email', () => {
    const out = validate.account({
      name: '  Priya Menon ', email: 'Priya@College.Example',
      password: 'canteen-demo',
    });
    assert.equal(out.name, 'Priya Menon');
    assert.equal(out.email, 'priya@college.example');
  });

  it('accepts a name written in Devanagari', () => {
    const out = validate.account({
      name: 'प्रिया मेनन', email: 'p@college.example',
      password: 'canteen-demo',
    });
    assert.equal(out.name, 'प्रिया मेनन');
  });

  it('reports every bad field at once', () => {
    assert.deepEqual(Object.keys(fieldsOf(() => validate.account({
      name: 'P', email: 'not-an-email', password: 'short',
    }))), ['name', 'email', 'password']);
  });

  it('refuses a body that is not an object at all', () => {
    const fields = fieldsOf(() => validate.account('hello'));
    assert.ok(fields.name && fields.email && fields.password);
  });

  it('refuses a password on the list of common ones', () => {
    const fields = fieldsOf(() => validate.account({
      name: 'Meera Joshi', email: 'm@college.example',
      password: 'password1',
    }));
    assert.equal(fields.password, 'This password is too common. '
      + 'Choose one that is hard to guess.');
  });

  it('refuses a common password whatever its capitals', () => {
    const fields = fieldsOf(() => validate.account({
      name: 'Meera Joshi', email: 'm@college.example',
      password: 'PassWord1',
    }));
    assert.ok(fields.password);
  });
});

describe('validate.menuItem', () => {
  it('needs every field for a new item', () => {
    const fields = fieldsOf(() => validate.menuItem({}));
    assert.deepEqual(Object.keys(fields),
      ['name', 'category', 'pricePaise', 'isVeg']);
  });

  it('takes any one field for a change', () => {
    assert.deepEqual(
      validate.menuItem({ pricePaise: 7500 }, { partial: true }),
      { pricePaise: 7500 });
  });

  it('refuses a change that changes nothing', () => {
    const fields = fieldsOf(() =>
      validate.menuItem({}, { partial: true }));
    assert.ok(fields.body);
  });

  it('refuses a price in rupees with a decimal point', () => {
    const fields = fieldsOf(() => validate.menuItem(
      { pricePaise: 75.5 }, { partial: true }));
    assert.ok(fields.pricePaise);
  });
});

describe('validate.idParam', () => {
  it('reads a plain whole number', () => {
    assert.equal(validate.idParam('17', 'Order'), 17);
  });

  for (const bad of ['0', '017', '-1', '1.5', 'abc', '4294967296']) {
    it(`treats "${bad}" as not found`, () => {
      assert.throws(() => validate.idParam(bad, 'Order'),
        { status: 404, message: 'Order not found.' });
    });
  }
});

describe('validate.dateQuery', () => {
  it('uses the fallback when no date is given', () => {
    assert.equal(validate.dateQuery(undefined, '2026-09-29'),
      '2026-09-29');
  });

  for (const bad of ['2026-02-30', '2026-13-01', '29-09-2026']) {
    it(`refuses ${bad}`, () => {
      assert.throws(() => validate.dateQuery(bad), { status: 400 });
    });
  }
});
munotes.in352

Unit Testing

Three habits in it are worth copying:

  • A helper for the awkward part. fieldsOf(fn) runs something that should be refused and returns the error's field messages, so each test is one line of intent. It also fails when nothing is thrown, which is the case everyone forgets: a test that expects a refusal must fail if the input is accepted.
  • The good case and the bad case together. The name is trimmed and the email lower-cased; a name in Devanagari is accepted; three bad fields are reported at once.
  • The messages are asserted, not just the failure. A student reads the message, so the message is part of the behaviour.
munotes.in353

Unit Testing

Testing the cryptography

'use strict';

const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { hashPassword, verifyPassword } = require('../../src/passwords');

describe('passwords', () => {
  it('stores the settings, the salt and the key', async () => {
    const stored = await hashPassword('lunch-at-12-40');
    const parts = stored.split('$');
    assert.equal(parts.length, 6);
    assert.deepEqual(parts.slice(0, 4),
      ['scrypt', '16384', '8', '5']);
  });

  it('accepts the right password and refuses a wrong one',
    async () => {
      const stored = await hashPassword('lunch-at-12-40');
      assert.equal(await verifyPassword('lunch-at-12-40', stored),
        true);
      assert.equal(await verifyPassword('lunch-at-12-50', stored),
        false);
    });

  it('salts: one password never gives the same hash twice',
    async () => {
      const a = await hashPassword('same-password');
      const b = await hashPassword('same-password');
      assert.notEqual(a, b);
    });

  it('refuses a stored value that is not an scrypt hash',
    async () => {
      assert.equal(await verifyPassword('x', 'plain-text'), false);
    });
});

What a test can and cannot say here is worth being clear about. It can say that the stored form carries the settings, that the right password verifies and a wrong one does not, that two hashes of one password differ, which is the salt working, and that a value that is not an scrypt hash is refused rather than crashing. It cannot say that scrypt is a good algorithm: that comes from OWASP's cheat sheet, and the decision is recorded in ADR-1 (Chapter 36). A test proves that the code does what was decided; the decision itself is defended in the documents.

Running them

$ cd ~/canteen-preorder
$ npm run test:unit 2>&1 | tail -4
  pass  refuses 2026-13-01
  pass  refuses 29-09-2026

43 tests: 43 passed, 0 failed

Forty-three tests, and they need no database and no server, which is why a developer runs them after every few lines. npm test runs these and the other seventy as well (Chapter 50).

What a failure looks like

A test is only useful if its failure is readable. One is broken on purpose here, by changing the cut-off from fifteen minutes to ten in a copy of the rules:

$ cd ~/canteen-preorder
$ sed -i 's/^const CUTOFF_MINUTES = 15;/const CUTOFF_MINUTES = 10;/' \
>   src/rules.js
$ node --test --test-reporter=./test/reporter.js \
>   test/unit/rules.test.js 2>&1 | grep -E 'FAIL|tests:'
  FAIL  closes each slot 15 minutes before it
  FAIL  is closed at the cut-off itself
14 tests: 12 passed, 2 failed
$ node --test --test-reporter=./test/reporter.js \
>   test/unit/rules.test.js 2>&1 | grep -A2 'closed at the cut-off'
  FAIL  is closed at the cut-off itself
        Expected values to be strictly equal:
$ sed -i 's/^const CUTOFF_MINUTES = 10;/const CUTOFF_MINUTES = 15;/' \
>   src/rules.js
$ node --test --test-reporter=./test/reporter.js \
>   test/unit/rules.test.js 2>&1 | tail -1
14 tests: 14 passed, 0 failed
munotes.in354

Unit Testing

Two tests fail, and their names say what changed: the cut-offs of all four slots, and the boundary at the cut-off itself. The test that checks a slot is open a minute before its cut-off still passes, because a slot that closes ten minutes before is still open at 12:24: the tests that fail are exactly the ones whose behaviour moved, which is what makes a failure worth reading.

The last two commands put the file back and show the tests passing again. A student trying this must remember the second half; a test suite left broken is worse than none, because it trains everybody to ignore it.

How many tests, and which

Not one test per function: one test per decision. The rules have 43 unit tests between four files because the rules make that many decisions, each of which somebody could get wrong:

  • every branch of a rule: open, closed, and the boundary between;
  • every limit, from both sides: 5 and 6 of an item, 10 and 11 in an order;
  • every kind of wrong input: the wrong type, missing, too long, not in the list;
  • every case the system exists to handle: a date that crosses midnight in another time zone, a password that is on the common list, a key that is blocked while another is not.

And one test for anything a bug has ever done, added the day it is fixed, so that it cannot come back (Chapter 56).

Do this for your project

  1. Write your rules as pure functions, and your small modules so that they are given what they need.
  2. Write unit tests with the code, not after.
  3. Name each test as a sentence about behaviour, so the report reads as a specification.
  4. Test both sides of every boundary and every branch.
  5. Give anything that depends on time a clock the test can set, and never make a test wait.
  6. Assert the message a user would see, not only that something failed.
  7. Break a test on purpose once, to see that its failure tells you what is wrong.

Mistakes that cost marks

Tests that need a database to test a rule, which is a sign the rule is in the wrong layer.

assert.ok(result) and nothing more, which passes for a dozen wrong answers.

A test that expects a refusal and passes when the input is accepted, because nothing checks that anything was thrown.

Tests named test1, test2, whose report tells nobody anything.

munotes.in355

Unit Testing

A test that sleeps, so the suite takes minutes.

Testing only the happy path, which is the one nobody gets wrong.

Quick revision

  • A unit is one function or one small module, tested in isolation, with no database, no server and no waiting.
  • Arrange, act, assert; the test's name is part of it.
  • Time: pass the moment to a pure function; give a movable clock to anything that remembers.
  • Test both sides of a boundary, every branch, every kind of wrong input, and every message.
  • A test that expects a refusal must fail when nothing is refused.
  • Tests say the code does what was decided; the decision is defended in the documents.

Questions you must be able to answer

1. What is a unit test, and what makes one possible? A test that calls one function or one small module on its own and checks the result, with no database, server or waiting. It is possible when the code is written so that it depends only on what it is given: pure functions, and modules handed their clock and their store.

2. How can a rule about a fifteen-minute cut-off be tested in a millisecond? By passing the moment in. The rule is a function of the time it is given, so a test can ask what is open at 12:24 and at 12:25 without waiting for either, and can test another time zone as easily.

3. How is the limit on failed sign-ins tested without waiting fifteen minutes? The limiter is given a clock. The test uses a clock of its own that only moves when the test moves it, so fifteen minutes pass with one call.

4. What can a unit test say about the password hashing, and what can it not? It can say that the stored form carries the algorithm and its settings, that the right password verifies and a wrong one does not, that the same password hashes differently each time, and that a stored value in the wrong form is refused. It cannot say that the algorithm is the right choice: that is a decision recorded and defended in the architecture document.

5. Why must a test that expects a refusal fail when nothing is refused? Because otherwise it passes when the system silently accepts something it should have rejected, which is exactly the fault it was written to catch. The helper that runs the call fails outright if no error was thrown.

6. Why break a test on purpose? To see that its failure says what is wrong. A suite whose failures are unreadable is a suite that will be ignored, and changing one rule should produce a small number of failures, each naming what it expected.

Contents This chapter on its own page

munotes.in356

Chapter Fifty-Two

Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables

Syllabus topic Module 2, "Integration & System Testing: Black-box testing".

In one line

Black-box testing derives its tests from what the system is supposed to do, never from how it is written: the values are divided into groups that should behave alike, one from each group is tried, the values either side of every edge are tried because that is where mistakes live, and where several conditions combine, a table lists the combinations so that none is forgotten.

In the wording to use when asked: black-box, or specification-based, testing derives test cases from the requirements without reference to the internal structure of the code. Equivalence partitioning divides the input domain into classes whose members should be treated alike, so that one value from each suffices; boundary value analysis adds the values at and immediately either side of each partition's edges, where off-by-one faults occur; and decision tables enumerate the combinations of conditions and the action expected of each.

Why derive tests from the specification

A test written by reading the code tends to agree with the code, including where the code is wrong. The mistake it cannot find is the one the programmer made twice: once in the code, once in the test.

So the tests in this chapter are written from the SRS alone (Chapter 34):

FR-7. Place an order. The system shall let a signed-in student order one or more items that can be ordered now, from 1 to 5 of each item and no more than 10 items in all, for one pickup slot.

Everything below comes out of that sentence and its neighbours, and would be the same whatever language the system were written in.

Equivalence partitioning

A partition is a set of values that should all be treated the same way. If 3 of an item is accepted, so are 2 and 4: trying all three tests the same thing three times. Divide the input into partitions, and test one value from each.

For the quantity of one item, FR-7 gives:

PartitionExampleExpected
below the range0refused
the valid range, 1 to 53accepted
above the range6refused
not a whole number2.5refused
not a number at all"2", null, truerefused

The last two partitions are the ones students forget, and they are where a careless system says yes: "2" is accepted by anything that converts before it checks (Chapter 47).

Boundary value analysis

Most faults are not in the middle of a partition but at its edge: < written for <=, a loop that stops one short. Boundary value analysis tests the edges and the values immediately either side.

For 1 to 5, the interesting values are 0, 1, 2, 4, 5, 6: just below, the lower edge, just inside, just inside the top, the upper edge, just above. Six tests, which is exactly what the file has:

munotes.in357

Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables

'use strict';

// Black-box tests of the order form's rules, derived from
// the requirement alone (SRS FR-7 and FR-8), not from the
// code: 1 to 5 of each item, at most 10 items in all, one of
// the four pickup slots.

const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const validate = require('../../src/validate');

const order = (items, slot = '12:40') => ({ slot, items });
const one = (quantity) => [{ menuItemId: 1, quantity }];

function accepted(body) {
  try {
    validate.order(body);
    return true;
  } catch (err) {
    if (err.status !== 400) throw err;
    return false;
  }
}

describe('quantity of one item: boundary values', () => {
  const cases = [
    [0, false], // just below the valid partition
    [1, true], // its lower edge
    [2, true], // just inside
    [4, true], // just inside the top
    [5, true], // its upper edge
    [6, false], // just above
  ];
  for (const [quantity, ok] of cases) {
    it(`${quantity} is ${ok ? 'accepted' : 'refused'}`, () => {
      assert.equal(accepted(order(one(quantity))), ok);
    });
  }
});

describe('quantity: the invalid partitions that are not numbers',
  () => {
    for (const q of [2.5, '2', null, undefined, true]) {
      it(`${JSON.stringify(q)} is refused`, () => {
        assert.equal(accepted(order(one(q))), false);
      });
    }
  });

describe('items in one order: boundary values', () => {
  const lines = (...qs) => qs.map((quantity, i) => ({
    menuItemId: i + 1, quantity,
  }));
  it('10 items in all is accepted', () => {
    assert.equal(accepted(order(lines(5, 5))), true);
  });
  it('11 items in all is refused', () => {
    assert.equal(accepted(order(lines(5, 5, 1))), false);
  });
  it('no items at all is refused', () => {
    assert.equal(accepted(order([])), false);
  });
  it('the same item twice is refused', () => {
    assert.equal(accepted(order([
      { menuItemId: 3, quantity: 1 }, { menuItemId: 3, quantity: 1 },
    ])), false);
  });
});

describe('pickup slot: every valid value and the invalid ones',
  () => {
    for (const slot of ['12:30', '12:40', '12:50', '13:00']) {
      it(`${slot} is accepted`, () => {
        assert.equal(accepted(order(one(1), slot)), true);
      });
    }
    // Built by hand, not with order(): its default parameter
    // would turn a missing slot into '12:40' and test nothing.
    for (const slot of ['12:35', '13:10', '', undefined]) {
      it(`${JSON.stringify(slot)} is refused`, () => {
        assert.equal(accepted({ slot, items: one(1) }), false);
      });
    }
  });

Read what else is in it:

  • The order's total size, FR-7's "no more than 10 items in all": 10 accepted, 11 refused. Two tests at the edge, and none in the middle.
  • Every valid slot, not one. There are only four (FR-8), so each is tried; the invalid ones are a time that is not a slot, a time after the last one, an empty value and none at all.
  • The comment that explains a trap. The helper order() has a default slot, so a test for a missing slot must not use it, or it would test 12:40 and pass for the wrong reason. A test that can pass for the wrong reason is worse than no test.
  • The same item twice is refused: a case no partition suggests, and which comes from reading FR-7 carefully.
munotes.in358

Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables

The test names are generated from the values, so the report reads as a table of what the system accepts:

$ cd ~/canteen-preorder
$ node --test --test-reporter=./test/reporter.js \
>   test/blackbox/order-input.test.js 2>&1 | head -18
test/blackbox/order-input.test.js
  pass  0 is refused
  pass  1 is accepted
  pass  2 is accepted
  pass  4 is accepted
  pass  5 is accepted
  pass  6 is refused
  pass  2.5 is refused
  pass  "2" is refused
  pass  null is refused
  pass  undefined is refused
  pass  true is refused
  pass  10 items in all is accepted
  pass  11 items in all is refused
  pass  no items at all is refused
  pass  the same item twice is refused
  pass  12:30 is accepted
  pass  12:40 is accepted

Decision tables

Some rules are not about one value but about a combination of conditions. Placing an order has four, and a fifth that depends on them:

ConditionFrom
Is someone signed in?FR-2
Are they a student?FR-7
Is the slot still open?FR-8
Do they already have an order for that slot?FR-10
Is there enough stock?FR-9

A decision table lists the combinations and what each should do. Written in full, five yes-or-no conditions would be 32 rules, most of them unreachable: if nobody is signed in, nothing else matters. So the table is written as rules that fire in order, each with the first condition that decides it:

RuleSigned inStudentSlot openNo order yetEnough stockExpected
R1yesyesyesyesyes201, the order
R2no----401 not_signed_in
R3yesno---403 forbidden
R4yesyesno--409 slot_closed
R5yesyesyesno-409 slot_taken
R6yesyesyesyesno409 sold_out

A dash means "it does not matter", and it is the heart of the technique: naming the combinations that cannot change the answer is what turns 32 rules into 6. Each row becomes one test, named after its rule:

'use strict';

// Black-box test of FR-7 to FR-9 as a decision table: which
// combination of conditions accepts an order, and which
// refusal each other combination gets. One test per rule.

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

const THALI = {
  slot: '12:40', items: [{ menuItemId: 1, quantity: 1 }],
};

describe('decision table: is an order accepted?', () => {
  let app;
  beforeEach(async () => {
    app = await startApp();
  });
  afterEach(() => app.stop());

  async function student() {
    const priya = app.client();
    await priya.signIn('priya@college.example');
    return priya;
  }

  it('R1 every condition met: accepted, 201', async () => {
    const res = await (await student()).post('/api/orders', THALI);
    assert.equal(res.status, 201);
  });

  it('R2 not signed in: 401', async () => {
    const res = await app.client().post('/api/orders', THALI);
    assert.equal(res.status, 401);
  });

  it('R3 signed in, but not as a student: 403', async () => {
    const ganesh = app.client();
    await ganesh.signIn('counter@college.example');
    const res = await ganesh.post('/api/orders', THALI);
    assert.equal(res.status, 403);
  });

  it('R4 slot closed: 409 slot_closed', async () => {
    const priya = await student();
    app.clock.set('2026-09-29T12:25:00+05:30');
    const res = await priya.post('/api/orders', THALI);
    assert.equal(res.status, 409);
    assert.equal(res.body.error.code, 'slot_closed');
  });

  it('R5 already has an order for the slot: 409 slot_taken',
    async () => {
      const priya = await student();
      await priya.post('/api/orders', THALI);
      const res = await priya.post('/api/orders', THALI);
      assert.equal(res.status, 409);
      assert.equal(res.body.error.code, 'slot_taken');
    });

  it('R6 not enough stock: 409 sold_out', async () => {
    const lata = app.client();
    await lata.signIn('owner@college.example');
    await lata.put('/api/menu/1/stock', { stockLeft: 0 });
    const res = await (await student()).post('/api/orders', THALI);
    assert.equal(res.status, 409);
    assert.equal(res.body.error.code, 'sold_out');
  });
});
munotes.in359

Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables

$ cd ~/canteen-preorder
$ node --test --test-reporter=./test/reporter.js \
>   test/blackbox/order-acceptance.test.js 2>&1 | tail -9
test/blackbox/order-acceptance.test.js
  pass  R1 every condition met: accepted, 201
  pass  R2 not signed in: 401
  pass  R3 signed in, but not as a student: 403
  pass  R4 slot closed: 409 slot_closed
  pass  R5 already has an order for the slot: 409 slot_taken
  pass  R6 not enough stock: 409 sold_out

6 tests: 6 passed, 0 failed

Three things about these tests:

  • They go through the API, not through a function, because the conditions they combine live in different layers: the guard, the rules and the store. A black-box test may use whatever door the specification describes.
  • Each sets up only its own condition: R4 moves the clock past the cut-off, R5 places an order first, R6 sets the stock to nothing. The rest stays as the seed data leaves it.
  • The expected value is the status and the code, which is what the specification promises (Chapter 30), not a message that may be reworded.

Choosing what to test, in order

Put together, the three techniques give a way of deriving a whole suite from a requirement, and an order to do it in:

  1. Partition every input, and include the partitions that are not the right kind at all.
  2. Take the boundaries of every range: the edge, and the value either side.
  3. Table the combinations where more than one condition decides the answer, and collapse the rules that cannot matter.
  4. Add the cases the specification names in words: "the same item twice", "one active order per slot".
  5. Add every case a bug has ever caused (Chapter 56).
munotes.in360

Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables

The worked project's 29 black-box tests come from exactly that, and they are the tests that would still be right if the whole application were rewritten in another language.

Do this for your project

  1. Write your black-box tests from your SRS, with the code out of sight.
  2. Partition every input; include the wrong-type and missing partitions.
  3. Test the edge of every range and the value either side of it.
  4. Build a decision table where conditions combine, and collapse the rules that cannot matter.
  5. Name each test after the value or the rule, so the report reads as a specification.
  6. Check that every test can only pass for the right reason; beware default values in your helpers.
  7. Assert the status and the code, not the wording of a message.

Mistakes that cost marks

Testing 1, 2 and 3 of a range that goes to 5, and never 5 or 6.

Only the happy path, with no test for the wrong type or a missing value.

Tests written from the code, which agree with it exactly where it is wrong.

A combination table with 32 rows, most of them impossible, instead of six that matter.

A test that passes because a helper filled in a value, not because the system behaved.

Asserting the English of a message, so rewording it breaks the suite.

Quick revision

  • Black box: tests from the specification, not the code.
  • Equivalence partition: one value from each group that should behave alike, including the wrong kinds.
  • Boundary values: the edge and either side; for 1 to 5, test 0, 1, 2, 4, 5, 6.
  • Decision table: the conditions, the rules, a dash for what cannot matter; 5 conditions collapse to 6 rules.
  • Name tests after their value or rule; assert status and code.
  • Beware a helper's default value letting a test pass for the wrong reason.

Questions you must be able to answer

1. What is black-box testing? Testing derived from what the system is specified to do, with no reference to how it is written, so the tests would be the same for any implementation and can catch a fault the programmer built into both the code and their own idea of it.

2. What is equivalence partitioning, and why does it save work? Dividing the possible inputs into groups whose members should all be treated the same way, and testing one value from each. Testing 2, 3 and 4 of an item tests one partition three times; testing 0, 3 and 6 tests three.

munotes.in361

Black-Box Testing: Equivalence Partitions, Boundary Values and Decision Tables

3. What is boundary value analysis, and which values does it choose for a range of 1 to 5? Testing the edges of each partition and the values immediately either side, because off-by-one faults live there: 0, 1, 2, 4, 5 and 6.

4. What is a decision table, and what does a dash in one mean? A table of the combinations of conditions that decide an outcome, with the expected result of each. A dash means the condition cannot change the result for that rule, which is what collapses an impractical number of combinations into a few that matter.

5. Why do the decision-table tests go through the API rather than call a function? Because the conditions they combine are enforced in different layers: whether someone is signed in, whether they are a student, whether the slot is open, and whether the stock is there. Only a request exercises all of them together.

6. Why does one test in the worked file avoid the helper that builds an order? Because the helper supplies a default slot, so a test for a missing slot would silently test the default and pass without testing anything. A test that can pass for the wrong reason gives false confidence.

Contents This chapter on its own page

munotes.in362

Chapter Fifty-Three

Integration Testing

Syllabus topic Module 2, "Integration & System Testing: Integration testing".

In one line

An integration test starts the real application with a real database and sends it real requests, so that it tests what a unit test cannot: that the route, the guard, the validation, the service, the store, the SQL and the schema agree with each other.

In the wording to use when asked: integration testing exercises components together rather than in isolation, to detect faults in their interfaces and interactions: incorrect assumptions between layers, mismatched data or column names, transactions that do not span what they should, and configuration that differs from the unit tests' stubs. Tests at this level drive the system through its real external interface and assert on its real persisted state.

What integration testing catches

A unit test says a function works when it is called correctly. Most real faults are about whether it is called correctly, and by what:

A fault of this kindA unit testAn integration test
the route validates the body but not the id in the addresspassescatches
the store selects total_paise AS totalCost but the service reads totalPaisepassescatches
the transaction wraps the insert but not the stockpassescatches
the guard is on the wrong routepassescatches
the schema's column is 60 characters and the validator allows 80passescatches
the cookie is set without HttpOnlypassescatches

Everything in that list is an interface: two parts, each right on its own, that disagree. That is what this level is for, and it is why the tests use the real database rather than a pretend one. A stub of the database would agree with whatever the code assumed, which is the assumption being tested.

How one is written

Every file is the same shape (the helper is Chapter 50's):

describe('the counter, the kitchen and the day report', () => {
  let app;
  let ganesh;
  beforeEach(async () => {
    app = await startApp();
    ganesh = app.client();
    await ganesh.signIn('counter@college.example');
  });
  afterEach(() => app.stop());
  • beforeEach, not before: the database is rebuilt and the application started again for every test, so no test can depend on what another left behind. It costs a fraction of a second and buys tests that can be run in any order, or alone.
  • The client keeps its cookie, so signing in once makes every later request that person's. Two clients in one test are two people, which is how the tests about one student and another are written.
  • afterEach stops the server and closes the pool, so a suite of six files does not leave six servers listening.

The orders' tests

'use strict';

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

const VEG_THALI = 1;
const VEG_BIRYANI = 2;
const CHOLE_BHATURE = 5; // switched off in the seed data
const SAMOSA = 7;

describe('placing and cancelling orders', () => {
  let app;
  let priya;
  beforeEach(async () => {
    app = await startApp();
    priya = app.client();
    await priya.signIn('priya@college.example');
  });
  afterEach(() => app.stop());

  async function stockOf(id) {
    const { body } = await app.client().get('/api/menu');
    return body.items.find((item) => item.id === id).stockLeft;
  }

  async function ownerSetsStock(id, stockLeft) {
    const lata = app.client();
    await lata.signIn('owner@college.example');
    await lata.put(`/api/menu/${id}/stock`, { stockLeft });
  }

  it('places an order and takes it from the stock', async () => {
    const res = await priya.post('/api/orders', {
      slot: '12:40',
      items: [
        { menuItemId: VEG_THALI, quantity: 2 },
        { menuItemId: SAMOSA, quantity: 1 },
      ],
    });
    assert.equal(res.status, 201);
    const { order } = res.body;
    assert.equal(order.slot, '12:40');
    assert.equal(order.pickupDate, '2026-09-29');
    assert.equal(order.status, 'placed');
    assert.equal(order.totalPaise, 16000);
    assert.equal(order.createdAt, '2026-09-29 10:30:00');
    assert.equal(order.items.length, 2);
    assert.equal(await stockOf(VEG_THALI), 58);
    assert.equal(await stockOf(SAMOSA), 99);
  });

  it('refuses more than is left, and takes nothing', async () => {
    await ownerSetsStock(VEG_BIRYANI, 2);
    const res = await priya.post('/api/orders', {
      slot: '12:40', items: [{ menuItemId: VEG_BIRYANI, quantity: 3 }],
    });
    assert.equal(res.status, 409);
    assert.equal(res.body.error.message, 'Only 2 Veg Biryani left.');
    assert.equal(await stockOf(VEG_BIRYANI), 2);
  });

  it('keeps nothing of an order that fails half way', async () => {
    await ownerSetsStock(VEG_BIRYANI, 2);
    const res = await priya.post('/api/orders', {
      slot: '12:40',
      items: [
        { menuItemId: VEG_THALI, quantity: 1 },
        { menuItemId: VEG_BIRYANI, quantity: 3 },
      ],
    });
    assert.equal(res.status, 409);
    // The thali was taken first, then given back by the
    // rollback when the biryani failed.
    assert.equal(await stockOf(VEG_THALI), 60);
  });

  it('refuses an item that is switched off', async () => {
    const res = await priya.post('/api/orders', {
      slot: '12:40',
      items: [{ menuItemId: CHOLE_BHATURE, quantity: 1 }],
    });
    assert.equal(res.status, 409);
    assert.equal(res.body.error.code, 'item_unavailable');
  });

  it("shows a student their own orders and hides everyone else's",
    async () => {
      const placed = await priya.post('/api/orders', {
        slot: '12:50', items: [{ menuItemId: SAMOSA, quantity: 2 }],
      });
      const id = placed.body.order.id;
      const mine = await priya.get('/api/orders/mine');
      assert.deepEqual(mine.body.orders.map((o) => o.id), [id]);
      const kabir = app.client();
      await kabir.signIn('kabir@college.example');
      assert.equal((await kabir.get(`/api/orders/${id}`)).status, 404);
      assert.equal((await kabir.post(`/api/orders/${id}/cancel`))
        .status, 404);
    });

  it('cancels a placed order and puts the food back', async () => {
    const placed = await priya.post('/api/orders', {
      slot: '12:30', items: [{ menuItemId: SAMOSA, quantity: 3 }],
    });
    const id = placed.body.order.id;
    const res = await priya.post(`/api/orders/${id}/cancel`);
    assert.equal(res.body.order.status, 'cancelled');
    assert.equal(await stockOf(SAMOSA), 100);
  });

  it('will not cancel an order already being prepared', async () => {
    const placed = await priya.post('/api/orders', {
      slot: '12:30', items: [{ menuItemId: SAMOSA, quantity: 1 }],
    });
    const id = placed.body.order.id;
    const ganesh = app.client();
    await ganesh.signIn('counter@college.example');
    await ganesh.patch(`/api/orders/${id}/status`,
      { status: 'preparing' });
    const res = await priya.post(`/api/orders/${id}/cancel`);
    assert.equal(res.status, 409);
    assert.equal(res.body.error.message, 'This order is being '
      + 'prepared, so it cannot be cancelled now.');
  });
});
munotes.in363

Integration Testing

Each test asserts on what is in the database afterwards, not only on what the API answered:

munotes.in364

Integration Testing

  • placing an order gives 201 with the right total and date, and the stock falls by exactly the quantities ordered;
  • an order for more than is left is refused and the stock is untouched;
  • an order whose second item is short leaves the first item's stock exactly as it was, which is the transaction of Chapter 45 seen from outside;
  • cancelling puts the food back;
  • an order already being prepared cannot be cancelled, and the message says why.

That last assertion is on a sentence a student reads, and it is deliberate: the wording of a refusal is part of the behaviour the SRS asks for (FR-13).

The counter's tests

'use strict';

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

const VEG_THALI = 1;
const SAMOSA = 7;

describe('the counter, the kitchen and the day report', () => {
  let app;
  let ganesh;
  beforeEach(async () => {
    app = await startApp();
    ganesh = app.client();
    await ganesh.signIn('counter@college.example');
  });
  afterEach(() => app.stop());

  async function orderAs(email, slot, items) {
    const c = app.client();
    await c.signIn(email);
    const res = await c.post('/api/orders', { slot, items });
    assert.equal(res.status, 201);
    return res.body.order.id;
  }

  const move = (id, status) =>
    ganesh.patch(`/api/orders/${id}/status`, { status });

  it('moves an order through every status', async () => {
    const id = await orderAs('priya@college.example', '12:40',
      [{ menuItemId: SAMOSA, quantity: 1 }]);
    for (const status of ['preparing', 'ready', 'collected']) {
      const res = await move(id, status);
      assert.equal(res.status, 200);
      assert.equal(res.body.order.status, status);
    }
  });

  it('refuses a skipped step', async () => {
    const id = await orderAs('priya@college.example', '12:40',
      [{ menuItemId: SAMOSA, quantity: 1 }]);
    const res = await move(id, 'ready');
    assert.equal(res.status, 409);
    assert.deepEqual(res.body.error.details,
      { from: 'placed', to: 'ready' });
  });

  it('refuses a status change from a student', async () => {
    const id = await orderAs('priya@college.example', '12:40',
      [{ menuItemId: SAMOSA, quantity: 1 }]);
    const priya = app.client();
    await priya.signIn('priya@college.example');
    const res = await priya.patch(`/api/orders/${id}/status`,
      { status: 'preparing' });
    assert.equal(res.status, 403);
  });

  it('lists one slot of the day for the counter', async () => {
    await orderAs('priya@college.example', '12:40',
      [{ menuItemId: SAMOSA, quantity: 1 }]);
    await orderAs('kabir@college.example', '12:50',
      [{ menuItemId: SAMOSA, quantity: 1 }]);
    const res = await ganesh.get('/api/orders?slot=12:40');
    assert.equal(res.body.date, '2026-09-29');
    assert.deepEqual(res.body.orders.map((o) => o.studentName),
      ['Priya Menon']);
  });

  it('adds up what the kitchen still has to make', async () => {
    const a = await orderAs('priya@college.example', '12:40', [
      { menuItemId: VEG_THALI, quantity: 2 },
      { menuItemId: SAMOSA, quantity: 1 },
    ]);
    await orderAs('kabir@college.example', '12:40',
      [{ menuItemId: VEG_THALI, quantity: 1 }]);
    const done = await orderAs('ananya@college.example', '12:40',
      [{ menuItemId: VEG_THALI, quantity: 4 }]);
    await move(a, 'preparing');
    for (const s of ['preparing', 'ready', 'collected']) {
      await move(done, s);
    }
    const res = await ganesh.get('/api/kitchen?slot=12:40');
    assert.deepEqual(res.body.items, [
      { name: 'Samosa', quantity: 1 },
      { name: 'Veg Thali', quantity: 3 },
    ]);
  });

  it('counts only collected orders as sales', async () => {
    const paid = await orderAs('priya@college.example', '12:40',
      [{ menuItemId: VEG_THALI, quantity: 2 }]);
    const gone = await orderAs('kabir@college.example', '12:40',
      [{ menuItemId: VEG_THALI, quantity: 1 }]);
    for (const s of ['preparing', 'ready', 'collected']) {
      await move(paid, s);
    }
    for (const s of ['preparing', 'ready', 'no_show']) {
      await move(gone, s);
    }
    const lata = app.client();
    await lata.signIn('owner@college.example');
    const { report } = (await lata.get('/api/reports/daily')).body;
    assert.deepEqual(report.byStatus, [
      { status: 'collected', orders: 1 },
      { status: 'no_show', orders: 1 },
    ]);
    assert.deepEqual(report.items,
      [{ name: 'Veg Thali', quantity: 2, revenuePaise: 14000 }]);
    assert.equal(report.revenuePaise, 14000);
  });

  it('keeps the day report to the owner', async () => {
    const res = await ganesh.get('/api/reports/daily');
    assert.equal(res.status, 403);
  });
});
munotes.in365

Integration Testing

The kitchen test is the one worth studying. It places three orders, moves one to being prepared and another all the way to collected, and then asks what the kitchen still has to make. The answer, 3 thalis and 1 samosa, is arithmetic across three tables and two statuses (FR-16), and there is no way to check it but to put real orders in a real database and ask.

The day report's test does the same for FR-17: only collected orders count as sales, so an order that was cancelled or not collected must not appear in the takings.

The sign-in tests

'use strict';

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

describe('accounts and sign-in', () => {
  let app;
  beforeEach(async () => {
    app = await startApp();
  });
  afterEach(() => app.stop());

  it('registers a student with a college address', async () => {
    const res = await app.client().post('/api/auth/register', {
      name: 'Meera Joshi', email: 'meera@college.example',
      password: 'a-long-password',
    });
    assert.equal(res.status, 201);
    assert.equal(res.body.user.role, 'student');
    assert.equal(res.body.user.passwordHash, undefined);
  });

  it('refuses an address at any other domain', async () => {
    const res = await app.client().post('/api/auth/register', {
      name: 'Meera Joshi', email: 'meera@gmail.com',
      password: 'a-long-password',
    });
    assert.equal(res.status, 400);
    assert.equal(res.body.error.details.email,
      'Use your @college.example address.');
  });

  it('refuses a common password, naming the field', async () => {
    const res = await app.client().post('/api/auth/register', {
      name: 'Meera Joshi', email: 'meera@college.example',
      password: 'sunshine1',
    });
    assert.equal(res.status, 400);
    assert.equal(res.body.error.details.password,
      'This password is too common. '
      + 'Choose one that is hard to guess.');
  });

  it('refuses an email that already has an account', async () => {
    const res = await app.client().post('/api/auth/register', {
      name: 'Priya Menon', email: 'priya@college.example',
      password: 'a-long-password',
    });
    assert.equal(res.status, 409);
    assert.equal(res.body.error.code, 'email_taken');
  });

  it('signs in, knows who is signed in, and signs out', async () => {
    const priya = app.client();
    const res = await priya.post('/api/auth/login', {
      email: 'priya@college.example', password: 'canteen-demo',
    });
    assert.equal(res.status, 200);
    const cookie = res.headers.get('set-cookie');
    assert.match(cookie, /^sid=[\w-]{43};/);
    assert.match(cookie, /HttpOnly/);
    assert.match(cookie, /SameSite=Lax/);
    assert.equal((await priya.get('/api/auth/me')).body.user.name,
      'Priya Menon');
    assert.equal((await priya.post('/api/auth/logout')).status, 204);
    assert.equal((await priya.get('/api/auth/me')).status, 401);
  });

  it('forgets a sign-in once its eight hours are up', async () => {
    const priya = app.client();
    await priya.signIn('priya@college.example');
    app.clock.set('2026-09-29T18:29:00+05:30');
    assert.equal((await priya.get('/api/auth/me')).status, 200);
    app.clock.set('2026-09-29T18:30:00+05:30');
    assert.equal((await priya.get('/api/auth/me')).status, 401);
  });

  it('answers a wrong password and an unknown email alike',
    async () => {
      const c = app.client();
      const wrong = await c.post('/api/auth/login', {
        email: 'priya@college.example', password: 'not-her-password',
      });
      const unknown = await c.post('/api/auth/login', {
        email: 'nobody@college.example', password: 'not-her-password',
      });
      assert.equal(wrong.status, 401);
      assert.equal(unknown.status, 401);
      assert.deepEqual(wrong.body, unknown.body);
    });

  it('refuses the sixth try after five wrong passwords', async () => {
    const c = app.client();
    const guess = { email: 'priya@college.example', password: 'guess' };
    for (let i = 0; i < 5; i += 1) {
      const res = await c.post('/api/auth/login', guess);
      assert.equal(res.status, 401);
    }
    const res = await c.post('/api/auth/login', guess);
    assert.equal(res.status, 429);
    // The countdown runs from the FIRST failure, so the wait
    // is a little under the whole 15 minutes by the time the
    // five guesses have been made and hashed.
    const wait = Number(res.headers.get('retry-after'));
    assert.ok(wait > 870 && wait <= 900, `retry-after was ${wait}`);
  });

  it('lets the owner create a counter staff account', async () => {
    const lata = app.client();
    await lata.signIn('owner@college.example');
    const made = await lata.post('/api/users/staff', {
      name: 'Meena Rane', email: 'meena@college.example',
      password: 'counter-side-door-7',
    });
    assert.equal(made.status, 201);
    assert.equal(made.body.user.role, 'staff');
    // The new account can sign in and see the counter's list.
    const meena = app.client();
    await meena.signIn('meena@college.example', 'counter-side-door-7');
    assert.equal((await meena.get('/api/orders')).status, 200);
  });

  it('keeps staff accounts to the owner', async () => {
    const priya = app.client();
    await priya.signIn('priya@college.example');
    const res = await priya.post('/api/users/staff', {
      name: 'Meena Rane', email: 'meena@college.example',
      password: 'counter-side-door-7',
    });
    assert.equal(res.status, 403);
    assert.equal(res.body.error.code, 'forbidden');
  });

  it('refuses a change that is not sent as JSON', async () => {
    const res = await app.client().post('/api/auth/login', {},
      { 'Content-Type': 'text/plain' });
    assert.equal(res.status, 415);
  });

  it('refuses a change sent from another website', async () => {
    const res = await app.client().post('/api/auth/logout', {},
      { Origin: 'https://evil.example' });
    assert.equal(res.status, 403);
    assert.equal(res.body.error.code, 'wrong_origin');
  });
});
munotes.in366

Integration Testing

These are the tests of the non-functional requirements that can be tested from outside:

  • NFR-4, the cookie: HttpOnly, SameSite=Lax, and a token of the right length are asserted on the real Set-Cookie header;
  • the eight hours: the test moves the clock forward and finds the session gone;
  • NFR-6, the limit: five failures and the sixth is refused, with a Retry-After in the right range;
  • S16: a wrong password and an unknown email answer alike;
  • S5: a common password is refused, with the message on the password field;
  • S7: a change that is not JSON, and one from another site, are both refused;
  • FR-3: the owner creates a counter staff account, who can then sign in and see the counter's list, and a student trying the same is refused.
munotes.in367

Integration Testing

The security tests

'use strict';

// Controls from the security design (Chapter 31) that no other
// test file proves. Each test is named after its control.

const crypto = require('node:crypto');
const { describe, it, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

describe('the security design', () => {
  let app;
  afterEach(() => app.stop());

  it('S6 keeps only a hash of the session token', async () => {
    app = await startApp();
    const res = await app.client().post('/api/auth/login', {
      email: 'priya@college.example', password: 'canteen-demo',
    });
    const token = res.headers.get('set-cookie')
      .match(/^sid=([^;]+)/)[1];
    const [rows] = await app.pool.query('SELECT id FROM sessions');
    const ids = rows.map((row) => row.id);
    assert.equal(ids.includes(token), false);
    assert.ok(ids.includes(
      crypto.createHash('sha256').update(token).digest('hex')));
  });

  it('S8 treats SQL typed into a field as text', async () => {
    app = await startApp();
    const res = await app.client().post('/api/auth/login', {
      email: "' OR '1'='1", password: "' OR '1'='1",
    });
    assert.equal(res.status, 401);
    const lata = app.client();
    await lata.signIn('owner@college.example');
    const name = "Tea'); DROP TABLE orders; --";
    const added = await lata.post('/api/menu', {
      name, category: 'drinks', pricePaise: 1500, isVeg: true,
    });
    assert.equal(added.status, 201);
    assert.equal(added.body.item.name, name);
    const ganesh = app.client();
    await ganesh.signIn('counter@college.example');
    assert.equal((await ganesh.get('/api/orders')).status, 200);
  });

  it('S10 refuses a request body over 10 kilobytes', async () => {
    app = await startApp();
    const priya = app.client();
    await priya.signIn('priya@college.example');
    const res = await priya.post('/api/orders', {
      slot: '12:40', items: [], note: 'x'.repeat(11000),
    });
    assert.equal(res.status, 413);
    assert.equal(res.body.error.code, 'too_large');
  });

  it('S11 answers a broken body without the details', async () => {
    app = await startApp();
    const res = await fetch(`${app.base}/api/auth/login`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: '{"email": ',
    });
    const text = await res.text();
    assert.equal(res.status, 400);
    assert.equal(JSON.parse(text).error.code, 'bad_json');
    assert.doesNotMatch(text, /SyntaxError|at JSON|stack/);
  });

  it('S12 marks the cookie Secure and sends HSTS under HTTPS',
    async () => {
      app = await startApp({ COOKIE_SECURE: 'true' });
      const res = await app.client().post('/api/auth/login', {
        email: 'priya@college.example', password: 'canteen-demo',
      });
      assert.match(res.headers.get('set-cookie'), /; Secure/);
      assert.equal(res.headers.get('strict-transport-security'),
        'max-age=15552000');
    });
});

Each is named after the control it proves (Chapter 31), so that the security report of Chapter 65 can be written by running the suite rather than by remembering. The SQL injection test is the one to look at: it stores a menu item named Tea'); DROP TABLE orders; -- and then checks that the orders table still answers, which is the demonstration of Chapter 44 turned into something that runs on every commit.

The menu's tests are the best file to read first, and its own name says why: "the menu, and the application as a whole". Twelve tests, of which the last five are not about the menu at all but about the application around it.

munotes.in368

Integration Testing

'use strict';

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const { startApp } = require('../helpers');

describe('the menu, and the application as a whole', () => {
  let app;
  let lata;
  beforeEach(async () => {
    app = await startApp();
    lata = app.client();
    await lata.signIn('owner@college.example');
  });
  afterEach(() => app.stop());

  it('shows the whole menu to anyone, meals first', async () => {
    const res = await app.client().get('/api/menu');
    assert.equal(res.status, 200);
    assert.equal(res.body.items.length, 14);
    assert.deepEqual(res.body.items[0], {
      id: 3, name: 'Chicken Biryani', category: 'meals',
      pricePaise: 11000, isVeg: false, isAvailable: true,
      stockLeft: 30,
    });
  });

  it('lets the owner add an item', async () => {
    const res = await lata.post('/api/menu', {
      name: 'Misal Pav', category: 'meals', pricePaise: 6000,
      isVeg: true,
    });
    assert.equal(res.status, 201);
    assert.equal(res.body.item.stockLeft, 0);
  });

  it('refuses a second item with the same name', async () => {
    const res = await lata.post('/api/menu', {
      name: 'Samosa', category: 'snacks', pricePaise: 2000,
      isVeg: true,
    });
    assert.equal(res.status, 409);
    assert.equal(res.body.error.code, 'name_taken');
  });

  it('lets the owner change a price and set the stock', async () => {
    await lata.patch('/api/menu/7', { pricePaise: 2500 });
    const res = await lata.put('/api/menu/7/stock', { stockLeft: 80 });
    assert.equal(res.body.item.pricePaise, 2500);
    assert.equal(res.body.item.stockLeft, 80);
  });

  it('keeps menu changes to the owner', async () => {
    const priya = app.client();
    await priya.signIn('priya@college.example');
    const change = { pricePaise: 100 };
    const asStudent = await priya.patch('/api/menu/7', change);
    const asStranger = await app.client().patch('/api/menu/7', change);
    assert.equal(asStudent.status, 403);
    assert.equal(asStranger.status, 401);
  });

  it('answers 404 for an item that does not exist', async () => {
    const res = await lata.patch('/api/menu/99', { pricePaise: 2500 });
    assert.equal(res.status, 404);
    assert.equal(res.body.error.message, 'Menu item not found.');
  });

  it('offers the four pickup slots with their cut-offs',
    async () => {
      const res = await app.client().get('/api/slots');
      assert.equal(res.status, 200);
      assert.deepEqual(res.body.slots, [
        { time: '12:30', cutoff: '12:15', open: true },
        { time: '12:40', cutoff: '12:25', open: true },
        { time: '12:50', cutoff: '12:35', open: true },
        { time: '13:00', cutoff: '12:45', open: true },
      ]);
    });

  it('closes the slots whose cut-off has passed', async () => {
    app.clock.set('2026-09-29T12:26:00+05:30');
    const { slots } = (await app.client().get('/api/slots')).body;
    assert.deepEqual(slots.map((s) => s.open),
      [false, false, true, true]);
  });

  it('reports itself up, with its database', async () => {
    const res = await app.client().get('/api/health');
    assert.deepEqual(res.body, { status: 'ok', database: 'ok' });
  });

  it('answers an unknown API address with JSON', async () => {
    const res = await app.client().get('/api/nothing-here');
    assert.equal(res.status, 404);
    assert.equal(res.body.error.code, 'not_found');
  });

  it('answers an unknown page with the 404 page', async () => {
    const res = await app.client().get('/nothing-here.html');
    assert.equal(res.status, 404);
    assert.match(res.text, /<title>Page not found/);
  });

  it('sends the security headers and hides the framework',
    async () => {
      const { headers } = await app.client().get('/api/menu');
      assert.match(headers.get('content-security-policy'),
        /default-src 'self'/);
      assert.equal(headers.get('x-content-type-options'), 'nosniff');
      assert.equal(headers.get('x-frame-options'), 'DENY');
      assert.equal(headers.get('x-powered-by'), null);
    });
});
munotes.in369

Integration Testing

Three habits in it are worth taking. The public route is tested without signing in at all, with a client that has no session, because that is how a student meets the menu before they have an account. Each refusal is checked as a pair: a student asking for the owner's change gets 403 and a stranger gets 401, which are different failures and are easy to confuse in the code. And the file tests the whole application while it is here: the health route, an unknown API address answering as JSON, an unknown page answering with the 404 page, and the security headers of Chapter 31, including that X-Powered-By is gone. Those five belong nowhere else, and putting them in one file is better than four half-used ones.

What an integration test catches that a unit test cannot

One way to see the point is to break something no unit test touches: the name of a column in the store.

$ cd ~/canteen-preorder
$ sed -i 's/o.total_paise AS totalPaise/o.total_paise AS totalCost/' \
>   src/store/orders.js
$ npm run test:unit 2>&1 | tail -1
43 tests: 43 passed, 0 failed
$ node --test --test-concurrency=1 \
>   --test-reporter=./test/reporter.js test/integration/orders.test.js \
>   2>&1 | grep -E 'FAIL|tests:' | head -4
  FAIL  places an order and takes it from the stock
7 tests: 6 passed, 1 failed
$ sed -i 's/o.total_paise AS totalCost/o.total_paise AS totalPaise/' \
>   src/store/orders.js
$ npm test 2>&1 | tail -1
117 tests: 117 passed, 0 failed

All forty-three unit tests still pass, because no unit test knows about the store. The integration test that reads an order back fails at once, and its name says where to look. One word changed in a SQL SELECT, a fault no amount of reading the services would show, caught in a second by the only level that asks the database.

Running them

$ cd ~/canteen-preorder
$ node --test --test-concurrency=1 \
>   --test-reporter=./test/reporter.js "test/integration/*.test.js" \
>   2>&1 | tail -3
  pass  S12 marks the cookie Secure and sends HSTS under HTTPS

45 tests: 45 passed, 0 failed

Forty-five tests, each against a freshly built database. They are slower than the unit tests, seconds rather than milliseconds, which is the price of testing the real thing, and the reason a developer runs npm run test:unit while writing and the whole suite before committing.

Do this for your project

  1. Test through your real interface, with your real database, in a database of its own.
  2. Rebuild that database before every test, and start the application from the same factory the server uses.
  3. Assert on the state afterwards, not only on the answer.
  4. Use one client per person, so that the tests read as people doing things.
  5. Cover every route, every guard and every refusal the API specification names.
  6. Name the tests of security controls after the controls, so the report can be produced by running them.
  7. Break something across a boundary once, and watch this level catch what the unit tests cannot.
munotes.in370

Integration Testing

Mistakes that cost marks

Mocking the database, which tests the mock's agreement with the code's assumptions.

One database for the tests and the application, emptied in the middle of a demonstration.

Tests that must run in order, because each leaves data for the next.

Servers left listening after the tests, until the port is taken and the suite fails for no reason.

Asserting only the status code, so a route that answers 200 with the wrong data passes.

No test for the refusals, which are most of what a real system does.

Quick revision

  • Integration tests catch interface faults: two parts, each right alone, that disagree.
  • The real database, in a database of its own, rebuilt before every test (beforeEach).
  • Same application factory as the server; one client per person, keeping its cookie.
  • Assert the answer and the stored state.
  • They test the NFRs visible from outside: cookie flags, session expiry, the sign-in limit, the security controls.
  • Slower than unit tests: run the unit tests while writing, all of them before committing.

Questions you must be able to answer

1. What does integration testing test that unit testing cannot? Whether the parts agree with each other: that routes validate what they receive, that the names the store selects are the names the service reads, that transactions cover what they should, that the guards are on the right routes, and that the schema's limits match the validator's.

2. Why use the real database rather than a stub? Because the thing being tested is the agreement between the code and the database: column names, types, constraints and transactions. A stub agrees with whatever the code assumed, so it can only confirm the assumption, never test it.

3. Why is the test database rebuilt before every test rather than every file? So that no test can depend on data another test left, and any test can be run alone or in any order. The cost is a fraction of a second; the benefit is that a failure means what it says.

4. What should an integration test assert? Both the answer and the state afterwards: the status and body the API returned, and what is now in the database, such as the stock having fallen by exactly the quantity ordered, or not having moved at all when the order was refused.

munotes.in371

Integration Testing

5. How do these tests cover non-functional requirements? By asserting what can be seen from outside: the cookie's flags and lifetime, a session that has expired, the refusal after five failed sign-ins with its Retry-After, identical answers for a wrong password and an unknown email, and each security control named in its test.

6. Give an example of a fault that only an integration test finds. A column renamed in a SQL SELECT, such as selecting total_paise AS totalCost where the service reads totalPaise. Every unit test still passes, because none of them touches the store, and every integration test that reads an order fails at once.

Contents This chapter on its own page

munotes.in372

Chapter Fifty-Four

System Testing and Acceptance

Syllabus topic Module 2, "Integration & System Testing", its last level: the whole system against the SRS, and acceptance by its users.

In one line

System testing asks whether the whole thing, deployed as it will be used, does what the SRS says, requirement by requirement, including the ones no unit test can reach: how it looks on a phone, whether a person can use it, and whether it works in the browsers people have; acceptance testing then puts it in front of the people who asked for it.

In the wording to use when asked: system testing evaluates the complete, integrated system against the specified requirements, functional and non-functional, in an environment as close to the operational one as possible; user acceptance testing is conducted by or on behalf of the users to determine whether the system satisfies their needs and is fit for its purpose, and its outcome is a decision to accept, accept with conditions, or reject.

What is left to test

By this point the suite covers every rule, every route and every refusal. Four things remain, and every one of them is a requirement:

RequirementWhy the suite cannot answer it
NFR-7: usable at 320 CSS pixels, an order in 4 taps, a single tap at the counterit is about a rendered page, not a response
NFR-8: labels, contrast, target size, keyboardthe same, and partly about what a person perceives
NFR-9: current Chrome, Firefox, Safari, Edge, and the appit is about other browsers
NFR-12: installs on Windows, macOS and Linuxit is about other machines

And one more thing no test of any kind can answer: will the canteen use it?

Testing the whole system against the SRS

System testing walks the SRS from FR-1 to FR-17 and NFR-1 to NFR-12 on the deployed system, doing each thing the way its user will. The worked team did it on the lab desktop between 6 and 12 October, from a written list of cases (Chapter 55), and recorded a result against every requirement, which becomes the last column of the traceability matrix.

Two habits make it worth the days it takes:

  • Use the system as its user does, on the device they will use: the counter's tests on the counter's tablet, the student's on a phone.
  • Do the whole job, not the step. "Place an order, then collect it" crosses four requirements, two roles and a status change, and that is where the seams are.

It was this, and not the 117 automated tests, that found the defect of Chapter 56: a refusal that read "Only 2 Chicken Biryani left. 3 2", because nothing until then had looked at a sentence a student reads.

Measuring NFR-7 and NFR-8

These two are requirements with numbers in them, so they are measured, not judged. Every page was opened in a browser at 320 and 390 CSS pixels wide, signed in as the person who uses it, and measured:

munotes.in373

System Testing and Acceptance

PageSideways scroll at 320Fields with a labelSmallest text contrastSmallest target
Sign innone5 of 57.31 to 145 px
Menunone1 of 16.93 to 124 px
My ordersnonenone on the page6.78 to 118 px, see below
Counternone1 of 17.31 to 124 px
Ownernone49 of 496.78 to 113 px, see below
  • Reflow (SC 1.4.10). At 320 pixels every page's content is exactly 320 wide: nothing has to be scrolled sideways, on any page, at either width.
  • Labels (SC 3.3.2). Every field on every page has one, the owner's 49 included: those are the price, stock and availability controls of each menu item, built by the page's script, which is exactly where a label is easiest to forget.
  • Contrast (SC 1.4.3). The lowest anywhere is 6.78 to 1, half as much again as the 4.5 the requirement asks for. It is the grey hint text, which is the text an author is most tempted to make too light.
  • Taps (NFR-7). From the menu page, a one-item order is two taps, the item's plus button and Place order, because the first open slot is already chosen; choosing another slot makes four. The limit is four.
  • The counter's single tap (NFR-7). Every action on the counter's screen is one button: Start preparing, Mark ready, Collected and paid, Not collected. Nothing there is typed.

The two targets under 24 pixels

A measurement that flags something is not a failure until the standard says so. SC 2.5.8 asks for 24 by 24 CSS pixels except in five cases, two of which apply here:

  • "See the menu", 100 by 18 pixels, on the orders page. It is a link inside the sentence "No orders today yet. See the menu.", whose line height is 24 pixels. The criterion's Inline exception covers a target "in a sentence or its size is otherwise constrained by the line-height of non-target text".
  • The owner's checkboxes, 13 by 13 pixels. The stylesheet deliberately leaves checkboxes as the browser draws them, which is the User Agent Control exception, "the size of the target is determined by the user agent and is not modified by the author". And in practice the target is larger anyway: each box sits inside a label measuring 92 by 44 pixels, and tapping the label toggles the box.

The honest way to record this is not to ignore the two numbers, and not to change the design to chase them, but to write both in the test report with the exception each relies on, so that a reader can check the reasoning. The rest of the page's targets have room to spare: the stepper's plus and minus are 44 by 44, Place order is 324 by 45, and the header's links are exactly 24 tall.

munotes.in374

System Testing and Acceptance

The keyboard (SC 2.1.1)

Every action must work from a keyboard, which for a page built this way means: every clickable thing is a real button or a real link. The pages' scripts attach every click handler to a button element, and nothing sets a tabindex. So the Tab key reaches every control in the order they appear, Enter and Space activate them, and the focus ring of Chapter 40's stylesheet shows where you are.

Testing the browsers (NFR-9)

Four browsers, and the Android app, each doing the same short list: sign in, order, see the order, and the counter's screen moving it on. What differs between browsers is rarely the logic; it is layout, dates, and whichever CSS feature is newest. The worked team ran the list on Chrome and Firefox on Windows, Safari on the MacBook, Edge on a lab machine, and the APK on two phones (Chapter 59), and recorded each as a line in the report.

Acceptance testing

Everything above is the team testing its own work. Acceptance testing is the users deciding, and it is a different thing: not "does it do what the SRS says" but "will we use this".

The worked team's session, at the canteen after the break on Friday 9 October, with Lata Pawar and Ganesh More, and the head cook watching:

WhatWhoResult
A1Set the day's stock for every itemthe ownerdone in four minutes, no help
A2Take twelve real pre-orders from students in the queuestudentsall twelve placed; one student ordered on the Android app
A3Work the counter through a whole slotGaneshall twelve moved to collected; the list refreshed itself
A4The kitchen list for the 12:40 slotthe head cookmatched what the counter had told him by voice
A5Read the day's reportthe ownertotals matched the cash in the drawer

What it found, which is what acceptance testing is for:

  • Ganesh asked for the order number to be bigger on the counter's list: at arm's length on a tablet, he was reading it twice. It was changed the same afternoon, and now draws at 24 pixels against the student name's 18.
  • The owner wanted the stock page to open on today's items only, which it already did; she had been looking at the menu page. A wording change, not a code change: "Menu and today's stock" became the heading.
  • The head cook asked whether he could have the kitchen list on paper. Out of scope, recorded in the report's limitations as the first thing a next version should do (Chapter 73).
munotes.in375

System Testing and Acceptance

The result: accepted. The owner agreed to run it for the rest of the term, which is the sentence that matters, and it goes in the report.

Two rules for a session like this. Watch, do not help: the moment you reach over and tap it for them, you have learned nothing. And write down what they say in their words, not your summary of it: "I have to read the number twice" is a finding, "minor UI issue" is not.

Do this for your project

  1. Walk your SRS, requirement by requirement, on the deployed system, doing each thing as its user would.
  2. Measure the requirements that have numbers, and write the numbers down.
  3. When a measurement flags something, read the standard before you call it a defect or change your design.
  4. Test the browsers and devices your users have, not only yours.
  5. Put the system in front of its real users, let them use it unaided, and watch.
  6. Record every finding in the user's own words, and what you did about it.
  7. Get the acceptance in writing, even if it is one line in an email.

Mistakes that cost marks

System testing by the person who wrote the code, on their own laptop, in the browser they develop in.

"It works on my phone", with no width, no browser and no number.

Ignoring a measurement that fails, or changing the design to chase a number without reading the criterion's exceptions.

Acceptance testing with the team driving, so the users watch a demonstration and agree to a system nobody tried.

No record of what the users said, so the report's "the client was satisfied" is the team's word for it.

Finding out on the examination day that the college's Edge draws one page differently.

Quick revision

  • System testing: the whole thing, deployed, against every requirement, as its user would.
  • What only it can reach: NFR-7 (320 px, taps), NFR-8 (labels, contrast, targets, keyboard), NFR-9 (browsers), NFR-12 (machines).
  • Measure, then read the standard: SC 2.5.8 has five exceptions, of which Inline and User Agent Control applied here.
  • Measured: no sideways scroll at 320; every field labelled; lowest contrast 6.78 to 1; an order in 2 taps; the counter's actions one tap each.
  • Acceptance testing is the users deciding: watch, do not help; record their words; get the acceptance written down.

Questions you must be able to answer

1. What does system testing add to unit and integration testing? It tests the whole system, deployed as it will be used, against every requirement, including the ones no automated test can reach: how the pages behave at a phone's width, whether a person can operate them, whether they work in other browsers, and whether the system installs elsewhere.

munotes.in376

System Testing and Acceptance

2. How were NFR-7 and NFR-8 measured rather than judged? Each page was opened in a browser at 320 and 390 CSS pixels, signed in as its user, and the page's own measurements were read: the width of its content against the window, whether every field has a label, the contrast of each text colour against its background by WCAG's formula, and the size of every target.

3. Two targets measured less than 24 by 24 pixels. Why is neither a failure? Because SC 2.5.8 lists exceptions. The link measuring 100 by 18 is inside a sentence, which the Inline exception covers. The 13 by 13 checkbox is drawn by the browser and not styled by the author, which the User Agent Control exception covers; and its label, which also toggles it, measures 92 by 44.

4. What is the difference between system testing and acceptance testing? System testing is the team checking the system against the specification. Acceptance testing is the users deciding whether they will use it, doing their own work with it, and its outcome is a decision rather than a pass or fail against a document.

5. Why should the team not help during an acceptance session? Because the thing being tested is whether the users can use the system without the team. Every time a developer reaches over and taps, a finding is lost, and the same difficulty will appear when nobody is there to help.

6. What did acceptance testing find in the worked project that no test had? That the order number on the counter's list was too small to read at arm's length on a tablet during service, and that a heading misled the owner about where to set the stock. Both came from watching real people do their own work.

Contents This chapter on its own page

munotes.in377

Chapter Fifty-Five

Test Case Preparation

Syllabus topic Module 2, "Integration & System Testing: Test case preparation".

In one line

A test case is one written instruction that anyone can carry out and get the same answer: what must be true first, what to do, what should happen, and what did; the set of them covers every requirement, which a traceability matrix proves; and the test summary report says what was run, what passed and what is still wrong.

In the wording to use when asked: a test case specifies preconditions, inputs or steps, and expected results for a single objective, with an identifier that lets it be referenced and traced to the requirement it verifies; a test suite is the organised set of cases, a traceability matrix demonstrates that every requirement is covered and every case is justified, and a test summary report records execution, results and outstanding defects.

The fields of a test case

FieldWhy it is there
Identifierso a report, a bug and a matrix can refer to it: TC-07
Requirementwhat it verifies: FR-9, NFR-2
Preconditionthe state before: "signed in as a student; 2 Veg Biryani left"
Stepsexactly what to do, in order, with the values
Expected resultwhat should happen, in enough detail to judge
Actual resultwhat did happen, filled in when it is run
Pass or failthe verdict
Notesthe defect's number, the browser, anything odd

A test case is not "test the order page". It is one objective, with values written down, that someone who has never seen the system can carry out and get the same answer. The test for that is simple: hand it to a member who did not write it and see whether they can run it without asking anything.

Automated or by hand

A test case in a document and a test in the suite are the same thing written twice. The worked project therefore writes each case once, in the form that suits it:

  • Where the objective can be checked by a program, the test is the automated test, and the document names it: TC-12 is orders.test.js, "refuses more than is left, and takes nothing".
  • Where it cannot, the case is written out in full for a person: the acceptance cases of Chapter 54, the browsers of NFR-9, the six-step installation of NFR-12.

That is why the test names in this project read as sentences (Chapters 51 to 53): a test named "refuses a skipped step" is its own test case.

From an acceptance criterion to a test case

Chapter 10 wrote each requirement's acceptance criteria as given, when, then. Those three words are the three fields of a test case, and turning one into the other is mechanical:

Given a student signed in, with 2 Veg Biryani left,

when they order 3 Veg Biryani for the 12:40 slot,

then the order is refused, the message says only 2 are left, and the stock is still 2.

munotes.in378

Test Case Preparation

FieldValue
IdentifierTC-12
RequirementFR-9
Preconditionsigned in as a student; Veg Biryani stock set to 2
StepsPOST /api/orders with slot 12:40 and 3 Veg Biryani
Expected409 sold_out; message "Only 2 Veg Biryani left."; stock unchanged at 2
Whereorders.test.js among the integration tests, the case named "refuses more than is left, and takes nothing"

This is the case that found the defect of Chapter 56. The automated test passed, because it checks the status, the message and the stock. Running the same case by hand, in a browser, showed the message as "Only 2 Chicken Biryani left. 3 2": the page was printing the error's details as well. A case is worth running both ways when what a person sees is part of the requirement.

The worked test case document

Twenty-six cases cover the seventeen functional requirements; a few of the more interesting ones:

IDReqPreconditionStepsExpected
TC-01FR-1nobody signed inregister with a college address and a good password201; the account is a student
TC-02FR-1nobody signed inregister with meera@gmail.com400; "Use your @college.example address." on the email field
TC-03FR-1nobody signed inregister with the password sunshine1400; "This password is too common." on the password field
TC-05FR-2, NFR-6nobody signed insign in wrongly five times, then a sixth429; Retry-After under 15 minutes
TC-08FR-8the clock at 12:26ask for the slots12:30 and 12:40 closed, 12:50 and 13:00 open
TC-12FR-92 Veg Biryani leftorder 3409 sold_out; stock still 2
TC-13FR-92 Veg Biryani leftorder 1 thali and 3 biryani409; the thali's stock unchanged
TC-16FR-10one order placed for 12:40order again for 12:40409 slot_taken
TC-18FR-13an order being preparedcancel it409 invalid_move; "This order is being prepared, so it cannot be cancelled now."
TC-21FR-15an order placedmove it straight to ready409 invalid_move
TC-23FR-16three orders for 12:40, one collectedask for the kitchen listonly what is still to make
TC-25NFR-25 plates lefttwenty students order at onceexactly 5 accepted, 15 refused, stock 0
TC-26NFR-12a clean machinethe six steps of Chapter 39the health check answers {"status":"ok"}

The last one is the installation check of Chapter 39, and it is a test case like any other: the worked team ran it on the lab desktop at the practice deployment on 16 September and on all four laptops on 25 September, and recorded six passes.

munotes.in379

Test Case Preparation

The traceability matrix, finished

Chapter 9 gave each requirement its source, the SRS its use case, and Chapter 35 the design that realises it. The last column is the test:

RequirementDesignTests
FR-1 RegisterPOST /api/auth/registerTC-01 to TC-03; auth.test.js (4 tests); validate.test.js
FR-2 Sign in and outPOST /api/auth/login, /logoutTC-04, TC-05; auth.test.js (3)
FR-3 Staff accountsPOST /api/users/staffTC-06; auth.test.js (2)
FR-4 Today's menuGET /api/menumenu.test.js "shows the whole menu to anyone, meals first"
FR-5 Menu itemsPOST, PATCH /api/menumenu.test.js (4)
FR-6 Today's stockPUT /api/menu/{id}/stockmenu.test.js "lets the owner change a price and set the stock"
FR-7 Place an orderPOST /api/ordersTC-09 to TC-11; order-input.test.js (29 tests); orders.test.js
FR-8 Pickup slotsGET /api/slotsTC-08; menu.test.js (2); rules.test.js (5)
FR-9 StockPOST /api/ordersTC-12, TC-13; orders.test.js (3); order-acceptance.test.js R6
FR-10 One order per slotPOST /api/ordersTC-16; order-acceptance.test.js R5; concurrency.test.js
FR-11 Order numberPOST /api/ordersTC-09; orders.test.js "places an order and takes it from the stock"
FR-12 My ordersGET /api/orders/mineorders.test.js "shows a student their own orders and hides everyone else's"
FR-13 Cancel an orderPOST /api/orders/{id}/cancelTC-17, TC-18; orders.test.js (2)
FR-14 The counter's listGET /api/orderscounter.test.js "lists one slot of the day for the counter"
FR-15 Moving an order onPATCH /api/orders/{id}/statusTC-20, TC-21; counter.test.js (3); rules.test.js (4)
FR-16 The kitchen listGET /api/kitchenTC-23; counter.test.js "adds up what the kitchen still has to make"
FR-17 Daily reportGET /api/reports/dailyTC-24; counter.test.js (2)

And the non-functional ones, which is where a matrix earns its keep, because they are the easiest to leave untested:

Verified by
NFR-1the load test of Chapter 63
NFR-2TC-25, concurrency.test.js
NFR-3passwords.test.js (4)
NFR-4auth.test.js: the cookie's flags, and the session after eight hours
NFR-5orders.test.js, menu.test.js, counter.test.js: each role refused what is not theirs
NFR-6TC-05, attempts.test.js (4), auth.test.js
NFR-7, NFR-8measured in Chapter 54
NFR-9the browser list of Chapter 54
NFR-10the service tests of Chapter 60
NFR-11one command, this chapter; the README, Chapter 69
NFR-12TC-26, six machines

What building it found

A matrix is worth building because of what it shows missing. Building this one found two endpoints of the API specification with no test at all:

  • GET /api/slots, the list of pickup slots and their cut-offs, which every student's page asks for first (FR-8). The rules behind it were tested; the endpoint was not.
  • POST /api/users/staff, the owner creating a counter staff account (FR-3), which the whole counter depends on and which no test had ever called.

Both had been used all through development, which is exactly why nobody noticed: a thing that works is not a thing that is tested. Four tests were written the same day, and the suite went from 113 to 117:

munotes.in380

Test Case Preparation

$ cd ~/canteen-preorder
$ npm test 2>&1 | grep -E 'slots|staff|tests:'
  pass  lets the owner create a counter staff account
  pass  keeps staff accounts to the owner
  pass  offers the four pickup slots with their cut-offs
  pass  closes the slots whose cut-off has passed
117 tests: 117 passed, 0 failed

Every endpoint of Chapter 30's table is now named in at least one test, which is the check to run on your own project:

$ cd ~/canteen-preorder
$ for e in auth/register auth/login auth/logout auth/me menu slots \
>     orders orders/mine kitchen reports/daily users/staff health; do \
>   n=$(grep -rl "api/$e" test/ | wc -l); \
>   printf '%-16s %s file(s)\n' "/api/$e" "$n"; done
/api/auth/register 2 file(s)
/api/auth/login  3 file(s)
/api/auth/logout 1 file(s)
/api/auth/me     1 file(s)
/api/menu        5 file(s)
/api/slots       1 file(s)
/api/orders      6 file(s)
/api/orders/mine 1 file(s)
/api/kitchen     1 file(s)
/api/reports/daily 1 file(s)
/api/users/staff 1 file(s)
/api/health      1 file(s)

The test summary report

One page at the end, which goes into the final report (Chapter 73):

Test summary, Canteen Pre-order, 15 October 2026

What was run. 117 automated tests (43 unit, 29 black-box, 45 integration) on Node.js 22, 24 and 26 with MySQL 8.0.46; 26 written test cases by hand on the lab desktop; the acceptance session of 9 October; the load test of 13 October; the security checks of 14 October.

Results. All 117 automated tests pass. 25 of the 26 manual cases passed at the first attempt; TC-12 failed on its message and was raised as issue 31 (Chapter 56), fixed the same day and re-run.

Coverage. Every functional requirement has at least one test; every endpoint and every error code of the API specification is exercised; the non-functional requirements are covered as the matrix above shows.

Outstanding. No defect of high severity is open. Two limitations are accepted and recorded in the report: the trial runs over plain HTTP on the college Wi-Fi, and there is no alerting when sign-ins fail in bursts.

Environment. The lab desktop, Ubuntu 24.04, MySQL 8.0.46, Node.js 24.21.0, behind Nginx; clients: Chrome and Firefox on Windows 11, Safari on macOS, Edge, and the Android app on two phones.

Four things make it a report rather than a claim: numbers, the environment, what failed and what happened to it, and what is still wrong. A report with no failures in it at all is not a sign of quality; it is a sign that the testing was written after the fact.

Do this for your project

  1. Write each case once: as an automated test where a program can judge it, as a written case where a person must.
  2. Turn every acceptance criterion into a case: given becomes the precondition, when the steps, then the expected result.
  3. Give every case an identifier, and name the requirement it verifies.
  4. Build the traceability matrix, and treat every empty cell as work, not as a formatting problem.
  5. Check that every endpoint and every error code is named in some test.
  6. Run the cases a person must judge in a browser, not only through the API.
  7. Write the summary report with numbers, the environment, the failures and what is still open.
munotes.in381

Test Case Preparation

Mistakes that cost marks

"Test the login page" as a test case, with no values, no precondition and no expected result.

Cases written after the testing, from memory, all marked pass.

A matrix with every cell filled and no test behind any of them.

No case for the non-functional requirements, which are the ones an examiner asks about.

A report with no failures, which nobody believes.

A requirement covered only by a test that was never run by hand, so nothing ever looked at what the user sees.

Quick revision

  • A test case: identifier, requirement, precondition, steps, expected, actual, verdict, notes.
  • Write it once: the automated test is the case where a program can judge it.
  • Given, when, then maps onto precondition, steps, expected.
  • The traceability matrix ends with the tests; an empty cell is missing work, and here it found two untested endpoints.
  • Check every endpoint and every error code is named by some test.
  • The summary report: what was run, results, coverage, what is outstanding, the environment.

Questions you must be able to answer

1. What are the fields of a test case, and why does each matter? An identifier, so it can be referred to; the requirement it verifies, for traceability; the precondition, so it starts from a known state; the steps with their values, so anyone can repeat it; the expected result, so the verdict is not a matter of opinion; the actual result and verdict when it is run; and notes, such as the defect it raised.

2. How does an acceptance criterion become a test case? Directly: "given" is the precondition, "when" is the steps, and "then" is the expected result. The criterion was written when the requirement was agreed, so the case tests what the client asked for rather than what the developer built.

3. Why write a test case once rather than in both a document and the suite? Because two records of the same thing drift apart, and then the document describes tests that no longer exist. Where a program can judge the result, the automated test is the case, and the document names it.

munotes.in382

Test Case Preparation

4. What does a traceability matrix prove, and what did building one find here? That every requirement is covered by at least one test and that every test exists for a reason. Building it found two endpoints of the API with no test at all, the slots list and the creation of staff accounts, both of which worked and neither of which was tested.

5. Why run a case by hand that an automated test already covers? Because an automated test checks what it was told to check. The sold-out case passed on the status, the message and the stock, while a person looking at the page saw a stray "3 2" on the end of the message: what the user sees is part of the requirement.

6. What makes a test summary report credible? Numbers, the environment it was run in, an honest account of what failed and what was done about it, and a list of what is still outstanding. A report with no failures suggests the testing was written afterwards.

Contents This chapter on its own page

munotes.in383

Chapter Fifty-Six

Bug Tracking

Syllabus topic Module 2, "Integration & System Testing: Bug tracking".

In one line

A bug report exists so that someone else can see the same fault: what was done, what happened, what should have happened, and where; and tracking them means every one has a number, a severity, an owner and a state, so that nothing is fixed twice and nothing is forgotten.

In the wording to use when asked: defect management records each defect with a unique identifier, a reproducible description, its severity and priority, the environment and the version in which it was observed, and tracks it through a defined lifecycle from new to closed, with the resolution and the verification recorded; the defect log is evidence both of the testing performed and of the quality of the delivered system.

What a bug report must contain

FieldThe worked example
Number31
TitleSold-out message ends with stray numbers: "Only 2 Chicken Biryani left. 3 2"
Wherethe menu page, placing an order; any refusal that carries details
Steps1. sign in as a student. 2. set Chicken Biryani's stock to 2 (as the owner). 3. order 3 of it
What happenedthe message area reads "Only 2 Chicken Biryani left. 3 2"
What should happen"Only 2 Chicken Biryani left."
Severitymedium: nothing is lost or wrong, but the message is confusing and looks broken
Found byAditi, system testing TC-12, 7 October, Chrome on Windows 11
Versionthe commit being tested that morning

The two lines that matter most are "what happened" and "what should happen". A report with only the first is a complaint; a report with only the second is a wish. Together they are a defect, and anyone can judge whether it is fixed.

And the steps must be exact. "It shows the wrong message sometimes" costs a day; the three steps above cost a minute, and whoever fixes it can prove the fix.

Severity and priority

They are different, and a tracker needs both:

  • Severity is how bad the fault is: does it lose data, stop the system, give a wrong answer, or merely look wrong?
  • Priority is when it will be fixed: the same fault matters more the week before the examination than in the first week of building.
SeverityMeaning hereExample
Criticaldata lost or wrong, or the system unusablean order accepted for stock that is not there
Higha requirement not met, with no way round itthe counter cannot move an order to ready
Mediuma requirement met badly, or a way round existsthis defect: the message is confusing
Lowcosmeticthe order number is smaller than Ganesh would like (Chapter 54)

A high-severity defect can be low priority if it is in a Could Have; a medium one can be top priority if the examiner will see it. The pass criteria of the test plan (Chapter 50) say which severities may still be open at the end: for the worked project, none above medium.

munotes.in384

Bug Tracking

The life of a bug

New when it is reported. Confirmed when someone else reproduces it, which is the step that catches a report that is really a misunderstanding. In progress when someone owns it. Fixed when there is a commit. Verified when the person who found it runs the case again and it passes. Closed only then.

Two states worth having, and using honestly: Won't fix, with the reason, for something real that the team decides not to do; and Not a bug, for behaviour that turns out to be correct. Both are better than a silently ignored issue.

Using GitHub Issues

MU makes GitHub mandatory (Chapter 1), and its issue tracker needs no setting up:

  • The title is the symptom, not the suspected cause: "Sold-out message ends with stray numbers", not "showErrors is broken". The cause is often wrong, and the symptom is what everyone recognises.
  • Labels carry the facts a filter needs: bug, severity: medium, area: frontend, and found: system testing.
  • One issue, one fault. Two faults in one issue can never be half-closed.
  • A template in .github/ISSUE_TEMPLATE gives everyone the same fields, so nothing is left out at midnight.
  • The commit closes it: a message with "Closes #31" links the fix to the report and closes the issue when it is merged, which is also what a guide reads at the code review (Chapter 49).

The board at the end of the project is evidence for the examiner: issues opened across the weeks, each closed by a commit, is a history no downloaded project has.

The worked defect, from report to closed

Reported. On 7 October, running TC-12 by hand in a browser, Aditi saw the message above. She wrote issue 31 as printed in the table.

Confirmed. Rohan reproduced it in a minute, and found a second case at once: after five failed sign-ins the message read "Too many failed sign-ins. Try again later. 900". Same fault, different screen. He added the second case to the issue rather than opening another, because one fix would cure both.

The cause. The page helper put every entry of the error's details on the page. For a refusal of input those details are the messages for each field, which is exactly what should be shown; for a refusal of anything else they are facts for the program, the item's id and the stock left, or the seconds to wait.

munotes.in385

Bug Tracking

The fix. One line, in the helper: only a refusal whose code is invalid_input carries per-field messages; every other refusal shows its message alone.

export function showErrors(form, err) {
  clearErrors(form);
  const fields = err.code === 'invalid_input' ? err.details : {};
  const unplaced = [];
  for (const [name, text] of Object.entries(fields)) {

Verified. Aditi ran TC-12 again, in the browser, and the message read "Only 2 Chicken Biryani left." Rohan ran the sign-in case. The issue was closed by the commit that made the change.

And a test was added, because a bug that has happened once can happen again:

    assert.equal(res.status, 409);
    assert.equal(res.body.error.message, 'Only 2 Veg Biryani left.');

That assertion is on the message, which is what the defect was about. The automated test that existed before checked the status and the stock, and would have passed for ever.

Why this one is worth studying

It is an ordinary defect, and it shows four things students meet:

  1. It was not in the code that was changed. The helper had been right for every case anyone had tried; a new kind of error made it wrong.
  2. The tests passed. All of them. Nothing was checking what a person reads.
  3. It was found by a human running a written case, which is the argument for Chapter 54's kind of testing in one sentence.
  4. The fix made the rule explicit, rather than special-casing the symptom: "only a refusal of input has per-field messages" is now a sentence in the code, with a comment saying why.

Do this for your project

  1. Open an issue for every defect, however small, and one issue per fault.
  2. Write the steps so that someone else can see it in a minute, with the values you used.
  3. Always write both what happened and what should have happened.
  4. Set severity and priority, and say in your test plan which severities may remain open.
  5. Have someone else confirm it before anyone fixes it.
  6. Close the issue with the commit that fixes it, and verify by running the case again.
  7. Add a test that fails on the old code, so the bug cannot come back.

Mistakes that cost marks

"It doesn't work" as a bug report.

A cause in the title, which sends the next reader to the wrong file.

Several faults in one issue, so it stays open for weeks.

Fixed without a test, so the same bug returns in the report's screenshots.

Issues closed with no commit, so nobody can see what changed.

A tracker with nothing in it at the end of a semester's work, which says either that nothing was tested or that nothing was recorded.

Quick revision

  • A report: number, title (the symptom), where, steps with values, what happened, what should happen, severity, who found it, environment, version.
  • Severity is how bad; priority is when. The test plan says which severities may stay open.
  • Life: new, confirmed, in progress, fixed, verified, closed; plus won't fix and not a bug, with reasons.
  • GitHub: labels, one issue one fault, a template, and "Closes #31" in the commit.
  • Every fix gets a test that fails on the old code.
  • The worked defect: every detail of an error printed on the page; every test passed; found by a person running a written case.
munotes.in386

Bug Tracking

Questions you must be able to answer

1. What must a bug report contain to be useful? A number, a title naming the symptom, where it happens, exact steps with the values used, what happened, what should have happened, the severity, who found it and how, and the environment and version. The two accounts, actual and expected, are what make it judgeable.

2. What is the difference between severity and priority? Severity is how serious the fault is in itself, from cosmetic to data loss. Priority is how soon it will be fixed, which depends on what else is happening: an ugly message the week of the examination may be fixed before a rare crash.

3. Why should someone other than the reporter confirm a defect? Because it catches reports that are really misunderstandings or environment problems, and because the second person often finds another case of the same fault, as happened here with the sign-in limit's message.

4. Why add a test when fixing a bug? Because the bug proves that nothing was checking that behaviour. A test that fails on the old code and passes on the new one makes the fix permanent; without it, the same fault can return in a later change.

5. The worked defect passed every automated test. What does that teach? That a suite checks only what it was told to check. These tests asserted the status, the code and the stock, and none of them looked at the sentence a student reads, which is why a person running a written case in a browser is a level of testing that cannot be skipped.

6. What does an examiner learn from your issue tracker? Whether the project was tested and managed over the semester: issues opened across the weeks, each with steps and a severity, confirmed by someone else, closed by a commit that names them, with tests added. It is evidence that cannot be produced at the end.

Contents This chapter on its own page

munotes.in387

Chapter Fifty-Seven

Local Hosting: Running the Application for the Whole Lab

Syllabus topic Module 2, "Deployment: Cloud deployment / Local hosting", the local half.

In one line

Local hosting means running the application on one machine on the college's own network so that everyone else can reach it: the same code as on a laptop, but listening on the network instead of on itself, started in production mode, with a fixed address people can type, and a way to keep it running.

In the wording to use when asked: local hosting deploys the application on an organisation's own hardware, reachable over its local network rather than the public internet; it requires binding the server to a routable interface, a stable address for clients, the production configuration and data, firewall rules permitting the service port, and a supervision mechanism so the process survives logout and restarts.

Why the trial is hosted locally

MU offers the choice: "Cloud deployment / Local hosting" (Chapter 38). The worked team hosts locally, and the reason is a constraint, not a preference: C-3, the system runs on the college's machine and network, because that is what the IT lab in-charge offered and what the principal's office agreed to (Chapter 6). The cloud comes in Chapter 58, with what it would cost.

Local hosting has real advantages for a trial: the data stays on the college's machine, there is nothing to pay, and the whole thing can be switched off by unplugging it. It has two costs, and both are recorded: students can order only on the college Wi-Fi, and there is no HTTPS, because a machine with no public name can have no certificate (Chapter 25).

The four things that change from a laptop

On a laptopOn the lab desktopWhy
HOST=127.0.0.1the server listens where others can reach itotherwise only that machine can
the development datathe real menu and the owner's own accountthe seed accounts are for development (Chapter 44)
npm run devthe service manager starts itit must survive logout and reboot (Chapter 60)
any porta port people can reach, with a firewall that allows itUbuntu blocks nothing by default, but many networks do

Listening on the network, and not before

The application listens on 127.0.0.1 unless told otherwise, and that default is a security control, not an oversight (Chapter 31, S14): a machine on a shared network should not offer a service to it by accident. The difference is one setting, and it can be seen:

$ cd ~/canteen-preorder
$ npm start > local.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ grep -o 'running at http://[0-9.]*:3000' local.log
running at http://127.0.0.1:3000
$ ADDR=$(hostname -I | awk '{print $1}'); echo "this machine is $ADDR"
this machine is 172.17.0.2
$ curl -s -m 3 http://$ADDR:3000/api/health || echo "(no answer)"
(no answer)
$ kill %1
$ HOST=0.0.0.0 npm start > open.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
[1]-  Terminated              npm start > local.log 2>&1
$ curl -s -m 3 http://$ADDR:3000/api/health || echo "(no answer)"
{"status":"ok","database":"ok"}
munotes.in388

Local Hosting: Running the Application for the Whole Lab

With the default, the machine's own network address gives no answer at all: the service exists only for that machine, and a phone would see nothing there. With HOST=0.0.0.0 the same address answers, which is what a phone on the college Wi-Fi will use. (The address here is the lab container's; on the lab desktop it is the college network's.)

In the real deployment the application still listens on 127.0.0.1, and Nginx, which does face the network, passes requests to it (Chapter 60). The demonstration above is the simplest form, and the one to use in a lab where nothing else is available; the version with Nginx is what the architecture document specifies, because it puts one program between the network and the application and is where HTTPS will go.

The address people type

A phone needs something to type. Three ways, in order of how well they work:

  1. The machine's address, http://10.3.14.22:3000. It works at once and changes when the machine gets a new one from the network, which is why the next two exist.
  2. A fixed address, asked for from whoever runs the network: the same number every day. The worked team asked the IT lab in-charge for one when the practice deployment showed the address changing after a reboot.
  3. A name on the college's own network, canteen.college.local, which the network's own name service can give. Easiest for students, and it needs the network's owner to add it.

Whichever it is, it goes on a printed card at the counter with a QR code, because nobody types an address correctly at 12:15. The worked team's card is in the user manual (Chapter 67).

Production mode

Two settings differ from a laptop, and both are in .env (Chapter 39):

  • NODE_ENV=production, which Express reads: it caches its view lookups and, more importantly here, does not send stack traces in its own error pages. The application's error handler already refuses to send details (Chapter 48), so this is a second layer, not the only one.
  • LOG_REQUESTS=true, so that the day's requests are on record. On the lab desktop the log goes to the service manager's journal (Chapter 60), where it can be read after the break.

And one that must not differ: DEMO_TIME stays empty. A server left with the clock frozen takes orders for slots that closed hours ago, and the server says so at start-up for exactly that reason (Chapter 43).

munotes.in389

Local Hosting: Running the Application for the Whole Lab

The data the canteen uses

The seed data is for development: six accounts, all with the same password, and a menu of fourteen items (Chapter 44). For a trial, three things change:

  1. The owner's own account, with a password she chooses, made by the setup and then used to create the counter's account through the page (FR-3).
  2. The real menu, which the owner enters herself. That is not a chore; it is the first real test of the owner's screen, and the worked team watched her do it at the acceptance session (Chapter 54).
  3. The demonstration accounts removed, because an account whose password is in a public repository is not an account. The seed file says so at the top.

The firewall

Ubuntu's own firewall, ufw, is not switched on by default, so a fresh machine allows the port. On a college machine that may not be true, and the two commands worth knowing are:

sudo ufw status
sudo ufw allow 80/tcp

ufw is not installed in this book's lab image, so those two lines are not run here; Chapter 60 configures the real server, where the port that matters is 80, Nginx's, and not 3000.

Keeping it running

A server started from a terminal dies when the terminal closes, and everything above is undone by one reboot. That is the subject of Chapter 60: a service, which the operating system starts at boot, restarts if it stops, and logs. Until then, npm start in a terminal that stays open is enough for a demonstration, and nothing more.

The practice deployment

Chapter 6's first risk was that nobody on the team had ever deployed anything on Linux, and the design review turned it into an action: Rohan would deploy the application on the lab desktop in week 8, 14 to 18 September, and report at the first increment review (Chapter 37). What that week found, all of it ordinary and all of it better found then than in October:

  • The machine's address changed after a reboot, which is what produced the request for a fixed one.
  • MySQL was not installed; the desktop had been reinstalled for the team, and apt install mysql-server needed the IT in-charge's password, which needed an appointment.
  • npm ci failed the first time, because the machine had Ubuntu's own Node.js 18 (Chapter 39), and the application asks for 22 or later.
  • The application would not start: DB_PASSWORD was not set, because .env is not in the repository (Chapter 31). Which is the design working, and worth ten minutes to learn.
  • The whole thing took two afternoons, against the four hours the plan allowed, and the plan's float absorbed it (Chapter 17).
munotes.in390

Local Hosting: Running the Application for the Whole Lab

Rohan wrote it up as a page of notes, and Farhan followed those notes for the real deployment on 6 October in under an hour. That is what a practice deployment is for: not to have a server, which is thrown away, but to have the notes.

Do this for your project

  1. Deploy once, early, on the machine you will use, long before it counts.
  2. Write down every step and every surprise as you go; those notes are the deliverable.
  3. Keep the application listening on the machine itself, and put a web server in front of it.
  4. Get a fixed address or a name from whoever runs the network, and put it on a card with a QR code.
  5. Use production settings and real data, and remove the demonstration accounts.
  6. Check the firewall before you conclude that your code is at fault.
  7. Never leave a demonstration clock set on a real server.

Mistakes that cost marks

The first deployment on the day of the demonstration.

HOST=0.0.0.0 with no firewall and the demonstration accounts still in place, which is a public system with a published password.

An address that changes, so the card at the counter is wrong by Thursday.

npm run dev as the deployment, dying with the terminal.

DEMO_TIME left set, so the server takes orders for slots that have closed.

The seed data in the trial, so the owner's menu is a list of examples from a book.

Quick revision

  • Local hosting: the college's own machine and network (C-3); no cost, data stays, but Wi-Fi only and no HTTPS.
  • The application listens on 127.0.0.1 by default; Nginx faces the network (Chapter 60).
  • Clients need a fixed address or a name, on a card with a QR code.
  • Production: NODE_ENV=production, logging on, DEMO_TIME empty.
  • Real data: the owner's own account, her own menu, the demonstration accounts removed.
  • Practice early: the notes, not the server, are the point.

Questions you must be able to answer

1. What does local hosting mean, and why did the worked team choose it? Running the application on the organisation's own machine, reachable over its own network rather than the internet. The team chose it because constraint C-3 required the college's machine and network, which is what the IT lab in-charge offered.

2. What changes between running on a laptop and hosting for the whole lab? Where the server listens, so that others can reach it; the data, which becomes the canteen's own; how it is started, by the operating system rather than a terminal; and the network's own rules, such as a firewall allowing the port.

munotes.in391

Local Hosting: Running the Application for the Whole Lab

3. Why does the application listen on 127.0.0.1 by default? So that it offers nothing to the network until someone decides that it should. On the real deployment it keeps that default, and Nginx, which does face the network, passes requests to it, so only one program is exposed.

4. Why must the demonstration accounts be removed before a trial? Because their password is in the repository, so they are not accounts at all: anyone who can read the project can sign in as the owner. The seed file exists for development and says so at the top.

5. What is the purpose of a practice deployment? To find out what will go wrong while there is time to deal with it, and to produce notes that make the real deployment quick. In the worked project it found a changing address, a missing database, the wrong version of Node.js and a missing settings file, none of which cost anything in September and all of which would have cost the demonstration in October.

6. Why must DEMO_TIME be empty on a real server? Because it freezes the application's clock, so the slot rules would be decided against a time that is not now: the system would take orders for slots that closed hours ago. The server prints a warning at start-up whenever it is set.

Contents This chapter on its own page

munotes.in392

Chapter Fifty-Eight

Cloud Deployment

Syllabus topic Module 2, "Deployment: Cloud deployment / Local hosting", the cloud half.

In one line

Deploying to the cloud means renting someone else's computer, with a public address and a name, so that the system is reachable from anywhere rather than only on the college Wi-Fi, which is also what makes HTTPS possible; the code does not change, only where it runs and what the deployment diagram says.

In the wording to use when asked: cloud deployment provisions computing resources from a provider rather than on owned hardware, as infrastructure as a service, where a virtual machine is administered by the team, or as a platform service, where the provider runs the runtime and the team deploys only the application; it brings a public address, a domain name, transport security with a trusted certificate, and a recurring cost.

What the cloud buys, and what it costs

The trial's two limits (Chapters 25 and 57) are both limits of the network, not of the code:

  • ordering works only on the college Wi-Fi, so a student on mobile data cannot order on the way in;
  • there is no HTTPS, because a machine with no public name can have no certificate a phone trusts.

A rented server with a public address fixes both, and brings a bill and a little administration. For the worked project it stays a plan rather than a deployment, because constraint C-2 says there is no money for hosting, and C-3 puts the system on the college's machine. A plan that is costed and not taken is a decision; the same plan uncosted is a gap.

Two ways to rent

A virtual machine (infrastructure)A platform service
What you geta whole Linux machine, emptysomewhere to push the code to
Who installs Node.js and MySQLyouthe provider
Who patches the operating systemyouthe provider
What the deployment diagram showsthe same nodes as Chapter 25, on a rented machinefewer nodes, and a managed database
What you learneverything in Chapter 60less, which is sometimes the point
Costlower, and fixedhigher, and often per-application

For this paper the virtual machine is the better choice, and not only because it is cheaper: MU's Module 2 names server configuration as a topic, and a platform service is exactly the thing that takes server configuration away.

What it costs

Prices as published on 30 September 2026, for DigitalOcean's Basic Droplets, which are typical of what a small provider charges for a whole small Linux machine:

MemoryvCPUTransferSSDA month
512 MiB1500 GiB10 GiB$4.00
1 GiB11,000 GiB25 GiB$6.00
2 GiB12,000 GiB50 GiB$12.00

The canteen needs the second of those: MySQL alone is happier with a gigabyte, and the application and Nginx together use very little. $6 a month is about Rs 575 at 30 September 2026's rate, or roughly Rs 6,900 for a year, against the Rs 9,500 the counter's tablet cost (Chapter 7). A domain name is a few hundred rupees a year more, and a certificate from Let's Encrypt is free.

munotes.in393

Cloud Deployment

Two things to notice about how that is quoted. The prices are in the provider's own currency, because that is what the page says and what the card is charged; the rupee figure carries the date and the rate, because both move. And DigitalOcean's page adds that from 1 January 2026 it bills per second, with a minimum charge of sixty seconds, which matters to a student: a server built for a demonstration and destroyed the same evening costs pennies.

What a student can get free

The GitHub Student Developer Pack is worth applying for with a college email address, and its own page listed these on 30 September 2026:

  • Microsoft Azure: "Free access to 25+ Microsoft Azure cloud services plus $100 in Azure credit. For students aged 18+", with "no credit card required".
  • Heroku: "Enjoy a credit of $13 USD per month for 24 months."
  • Namecheap: "1 year domain name registration on the .me TLD" and "1 SSL certificate free for 1 year."

A hundred dollars of credit is more than a year of the server above. Check the pack yourself: the offers change, and this book's list is a snapshot with a date on it. The Namecheap domain is the piece students most often miss, and a name is what makes HTTPS possible at all.

What changes in the project

Almost nothing, which is the point of having kept every setting out of the code (Chapter 28):

On the lab desktopOn a rented server
HOST127.0.0.1, behind Nginx127.0.0.1, behind Nginx
COOKIE_SECUREfalsetrue, because HTTPS is real
TRUST_PROXYloopbackloopback
DB_PASSWORDthe lab'sa different one, on the server only
Nginxport 80, HTTPport 443, HTTPS, with port 80 redirecting to it
The certificatenoneLet's Encrypt, renewed automatically
The addressthe machine's, on the college Wi-Fia name, from anywhere

COOKIE_SECURE=true is the one that matters for the security design: the sign-in cookie is then never sent over plain HTTP, and the application also sends the header that tells browsers to use HTTPS for the site in future (Chapters 31 and 46). It is one line in .env, and its test already exists (S12, Chapter 53).

The deployment diagram, redrawn

Chapter 25 drew where the system runs during the trial. The cloud version is the same diagram with three changes: the lab desktop becomes a virtual machine in a data centre, the phones reach it over the internet by name on port 443, and the Wi-Fi limit disappears.

munotes.in394

Cloud Deployment

A deployment diagram: the student's phone, holding canteen.apk, and the counter device both reach Nginx over HTTPS on port 443, from anywhere, on Wi-Fi or mobile data; Nginx runs inside Ubuntu 24.04 on a rented 1 GiB server, with Node.js 24 holding canteen-preorder and MySQL 8.0 holding the canteen database, reached on 127.0.0.1 ports 3000 and 3306

Figure 58.1 Where the system would run on a rented server

@startuml deployment-cloud
!pragma layout smetana
node "Student's phone" <<device>> as Phone {
  artifact "canteen.apk" as Apk
}
node "Counter device" <<device>> as Counter
node "Rented server\n(1 GiB, Ubuntu 24.04)" <<device>> as Server {
  node "Ubuntu 24.04" <<executionEnvironment>> as Os {
    node "Nginx" <<executionEnvironment>> as Nginx
    node "Node.js 24" <<executionEnvironment>> as Node {
      artifact "canteen-preorder" as App
    }
    node "MySQL 8.0" <<executionEnvironment>> as Db {
      artifact "canteen\ndatabase" as Data
    }
  }
}
Phone --> Nginx : HTTPS 443, from anywhere\n(Wi-Fi or mobile data)
Counter --> Nginx : HTTPS 443
Nginx --> App : HTTP\n127.0.0.1:3000
App --> Data : TCP\n127.0.0.1:3306
@enduml

The environments and the artifacts inside are identical: Nginx, Node.js with the application, MySQL with the database. That is the reward for keeping the application and the database off the network (Chapter 25): moving to the cloud changes the outermost box and nothing within it.

What a student must do that the college did for them

On the college's machine, the IT in-charge owned the machine, its network and its patches. On a rented server, the team owns all three, and three things follow:

  1. The machine is on the public internet from the minute it exists. Within hours, automated programs will be trying to sign in to it. Chapter 60's firewall and SSH keys are not optional there.
  2. Someone must apply security updates, or the machine becomes a liability to everyone.
  3. The bill continues after the project ends. Destroy the server when the examination is over, and keep the code, which is the thing that matters.

Do this for your project

  1. Decide honestly whether you need the cloud. If a local machine meets your constraints, say so and cost the alternative anyway.
  2. Prefer a small virtual machine for this paper: it is what teaches server configuration, and it is cheaper.
  3. Apply for the GitHub Student Developer Pack with your college email, and read the offers on the day you need them.
  4. Quote prices from the provider's own page, with the date; convert with one dated rate if your report is in rupees.
  5. Change settings, not code: the same application should run in both places.
  6. Turn on COOKIE_SECURE and HTTPS the moment you have a name.
  7. Destroy the server when you no longer need it, and write the date in your report.

Mistakes that cost marks

"We will deploy to the cloud" with no provider, no size and no price.

A price with no date, in a report read six months later.

munotes.in395

Cloud Deployment

A rupee figure with no rate, which cannot be checked or updated.

Claiming an offer the provider does not make, such as a student credit from a company that is not in the pack.

Code that must be edited to deploy, because settings live in it.

A server left running for months after the project, on a card nobody watches.

Quick revision

  • The cloud buys a public address, a name and therefore HTTPS; it costs a monthly bill and administration.
  • Virtual machine (you install everything) against a platform service (the provider does): for this paper, the machine, because server configuration is on the syllabus.
  • Published prices, 30 Sep 2026: $4, $6, $12 a month for 512 MiB, 1 GiB, 2 GiB; per-second billing since 1 Jan 2026.
  • Student pack on the same date: Azure $100 credit, Heroku $13 a month for 24 months, Namecheap a .me domain and one SSL certificate.
  • Moving changes settings, not code: COOKIE_SECURE=true, Nginx on 443, a certificate.
  • On a public server you own the firewall, the updates and the bill.

Questions you must be able to answer

1. What does a cloud deployment give a project like this that local hosting cannot? A public address and a domain name, so the system can be used from anywhere rather than only on the college network, and with them a certificate from a public authority, which is what makes HTTPS possible.

2. What is the difference between renting a virtual machine and using a platform service? With a virtual machine you get an empty Linux machine and install and maintain everything on it; with a platform service the provider runs the runtime and often the database, and you deploy only the application. The first teaches, and is examined by this paper's server-configuration topic; the second is quicker and usually costs more.

3. What would the worked system cost in the cloud, and how should that be quoted? About $6 a month for a 1 GiB machine at the prices published on 30 September 2026, with a domain name extra and the certificate free. It should be quoted in the provider's own currency with the date read, and converted to rupees only with a stated rate and date, because both prices and rates move.

4. What changes in the application when it moves to a rented server? Nothing in the code. The settings change: the cookie is marked Secure because the site is served over HTTPS, the database password is different, and Nginx serves port 443 with a certificate instead of port 80.

5. Why is COOKIE_SECURE false on the college's machine and true in the cloud? Because it tells the browser to send the sign-in cookie only over HTTPS. On the trial there is no HTTPS, so setting it would stop the cookie being sent at all; in the cloud there is, and it closes the trial's largest security weakness.

munotes.in396

Cloud Deployment

6. What responsibilities does a team take on with a public server that the college carried for them? Keeping it locked down from the moment it exists, since it will be probed within hours; applying security updates; and paying for it until it is destroyed, which should be done when the project no longer needs it.

Contents This chapter on its own page

munotes.in397

Chapter Fifty-Nine

APK Build: Putting the Application on an Android Phone

Syllabus topic Module 2, "Deployment: ... APK build".

In one line

An APK is the file an Android phone installs, and for a project whose pages already work on a phone the shortest honest way to make one is a WebView app: a window that shows your own pages, so that the rules stay on the server and a change to the pages needs no new app.

In the wording to use when asked: an Android application package (APK) is the archive containing an app's compiled code, resources and manifest, signed with a developer key; a WebView-based application embeds the platform's browser engine to present a web application as an installed app, which suits systems whose interface is already responsive web pages and whose logic is served by an API.

Why a WebView app, and when not

ADR-1 chose it, and recorded its cost (Chapter 36): the app is a wrapper around the web pages, not a native app: it works only with the server. That is the honest trade:

A WebView appA native app
What you writeone screen of Kotlinevery screen again
What a page change needsnothing; the server serves ita new APK, and everyone must update
Offlinenothing workscan be made to work
The phone's own things: camera, notificationsneed extra worknatural
For this projectthe pages already work on a phonewould double the work for no requirement

Choose a native app when a requirement needs the phone, not because an app sounds better than a website. No requirement here does; FR-12 wants a student to see their order's status, and a page shows that.

The Android project

It lives beside the server's code, in android/, and is its own Gradle project. Four files carry all of it.

What to build against and what to build with. The Android Gradle plugin, Kotlin, and the versions of Android the app compiles against and supports:

plugins {
  id("com.android.application")
  id("org.jetbrains.kotlin.android")
}

android {
  namespace = "example.college.canteen"
  compileSdk = 35

  defaultConfig {
    applicationId = "example.college.canteen"
    minSdk = 24          // Android 7, which every phone in the trial had
    targetSdk = 35
    versionCode = 1
    versionName = "1.0"
    // The one thing a student changes: where the canteen's
    // server is. It becomes BuildConfig.SERVER_URL below.
    buildConfigField("String", "SERVER_URL",
      "\"http://10.3.14.22:3000\"")
  }

  buildFeatures {
    buildConfig = true
  }

  // The release key is read from the environment, so no
  // keystore and no password is ever committed (Chapter 59).
  signingConfigs {
    create("release") {
      val store = System.getenv("CANTEEN_KEYSTORE")
      if (store != null) {
        storeFile = file(store)
        storePassword = System.getenv("CANTEEN_KEYSTORE_PASSWORD")
        keyAlias = System.getenv("CANTEEN_KEY_ALIAS") ?: "canteen"
        keyPassword = System.getenv("CANTEEN_KEY_PASSWORD")
          ?: System.getenv("CANTEEN_KEYSTORE_PASSWORD")
      }
    }
  }

  buildTypes {
    release {
      isMinifyEnabled = false
      if (System.getenv("CANTEEN_KEYSTORE") != null) {
        signingConfig = signingConfigs.getByName("release")
      }
    }
  }

  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
  }

  kotlinOptions {
    jvmTarget = "17"
  }
}

dependencies {
  implementation("androidx.appcompat:appcompat:1.7.0")
}
munotes.in398

APK Build: Putting the Application on an Android Phone

  • minSdk = 24 is Android 7. Every phone in the trial ran it or later, which is the only test that matters: ask your users what they have.
  • buildConfigField puts the server's address into the code at build time, as BuildConfig.SERVER_URL. It is the one line a student changes for their own canteen, and it is in the build file rather than in the Kotlin, so that a release for a different server is a different build, not a different program.
  • The signing block reads the key from the environment. No keystore, and no password, is ever in the repository (see below).

What the app asks the phone for, which is one permission:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- The app does nothing but show the canteen's own pages,
       so the internet is the only permission it asks for. -->
  <uses-permission android:name="android.permission.INTERNET" />

  <application
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:theme="@style/Theme.AppCompat.NoActionBar"
      android:usesCleartextTraffic="true"
      android:networkSecurityConfig="@xml/network_security_config">
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:configChanges="orientation|screenSize|keyboardHidden">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

android:usesCleartextTraffic and the network security configuration deserve their own paragraph. Since Android 9, an app may not use plain HTTP at all unless it says so, and this app must, because the trial's server has no certificate (Chapter 25). The right way to say so is not to allow it everywhere but to allow it for that one address:

<?xml version="1.0" encoding="utf-8"?>
<!-- The trial runs over plain HTTP on the college Wi-Fi
     (Chapter 25), which Android forbids by default. This
     allows it for the canteen's server ONLY; everything else
     must still be HTTPS. Delete this file when the server has
     a name and a certificate (Chapter 58). -->
<network-security-config>
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="false">10.3.14.22</domain>
  </domain-config>
  <base-config cleartextTrafficPermitted="false" />
</network-security-config>

When the server moves to a name with HTTPS (Chapter 58), that file is deleted and the app is stricter than the default again.

The app itself, which is one screen:

package example.college.canteen

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

// The whole app: a WebView showing the canteen's own pages,
// and a plain message when the server cannot be reached.
// Nothing about the canteen's rules lives here; the app is a
// window, so a change to the pages needs no new APK.
class MainActivity : AppCompatActivity() {

    private lateinit var web: WebView
    private lateinit var offline: LinearLayout

    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(saved: Bundle?) {
        super.onCreate(saved)

        web = WebView(this)
        // The pages are our own, and they need JavaScript.
        web.settings.javaScriptEnabled = true
        web.settings.domStorageEnabled = true
        web.webViewClient = object : WebViewClient() {
            // Keep the canteen's own pages inside the app, and
            // hand any other address to the phone's browser.
            override fun shouldOverrideUrlLoading(
                view: WebView, request: WebResourceRequest
            ): Boolean {
                val ours = request.url.toString()
                    .startsWith(BuildConfig.SERVER_URL)
                if (ours) return false
                startActivity(
                    android.content.Intent(
                        android.content.Intent.ACTION_VIEW, request.url))
                return true
            }

            override fun onReceivedError(
                view: WebView, request: WebResourceRequest,
                error: WebResourceError
            ) {
                if (request.isForMainFrame) showOffline()
            }
        }

        offline = buildOfflineView()
        setContentView(web)
        web.loadUrl(BuildConfig.SERVER_URL)
    }

    // The back button walks the pages' own history first, so
    // the app behaves as a browser does.
    @Deprecated("Deprecated in Java")
    override fun onBackPressed() {
        if (web.canGoBack()) web.goBack() else super.onBackPressed()
    }

    private fun showOffline() {
        setContentView(offline)
    }

    private fun buildOfflineView(): LinearLayout {
        val box = LinearLayout(this)
        box.orientation = LinearLayout.VERTICAL
        box.setPadding(48, 96, 48, 48)
        val words = TextView(this)
        words.text = getString(R.string.cannot_reach)
        words.textSize = 18f
        val again = Button(this)
        again.text = getString(R.string.try_again)
        again.setOnClickListener {
            setContentView(web)
            web.loadUrl(BuildConfig.SERVER_URL)
        }
        box.addView(words)
        box.addView(again)
        return box
    }
}
munotes.in399

APK Build: Putting the Application on an Android Phone

Four decisions in eighty-eight lines:

  • JavaScript is on, because the pages are ours and they need it. A WebView that loaded other people's pages with JavaScript on would be a different and much more careful program.
  • Our own addresses stay in the app; any other address goes to the phone's browser. Without that, a link to a bank in a page would open inside an app that looks like the canteen's.
  • A failure to load shows a plain message and a Try again button, in words a student can act on: check the college Wi-Fi. This is the app's only screen of its own.
  • The back button walks the pages' history first, so the app behaves the way a phone user expects.

Notice what is not in it: no rules, no prices, no statuses. Everything the canteen decides stays on the server, which is what makes the app a window.

The three small files that make it a Gradle project, and the strings the app shows:

// The one module: the app.
pluginManagement {
  repositories {
    google()
    mavenCentral()
    gradlePluginPortal()
  }
}
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
  }
}

rootProject.name = "CanteenPreorder"
include(":app")
// Plugins for the whole project; the app module applies them.
plugins {
  id("com.android.application") version "8.7.3" apply false
  id("org.jetbrains.kotlin.android") version "2.0.21" apply false
}
org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
kotlin.code.style=official

# Where the release key is, read from the environment or from
# a file OUTSIDE the repository. Never commit a keystore.
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_name">Canteen Pre-order</string>
  <string name="cannot_reach">Cannot reach the canteen server.
    Check that you are on the college Wi-Fi, then try again.</string>
  <string name="try_again">Try again</string>
</resources>

Every word a person reads is in that last file rather than in the code, which is how an app is translated later without touching a line of Kotlin.

munotes.in400

APK Build: Putting the Application on an Android Phone

Building it

Android Studio builds it with a button, and that is what the worked team used (Chapter 18). The same build from a terminal, which is what this book runs:

cd android
./gradlew assembleDebug

The result is app/build/outputs/apk/debug/app-debug.apk, about 3 MB, signed with the debug key that every Android installation has. That is the file to put on a phone for testing, and it is not the file to hand in: anyone's debug key is the same, so it proves nothing about who built it.

A signed release

A release APK is signed with a key you make and keep. Making one, and building with it:

keytool -genkeypair -v -keystore canteen-release.jks \
  -alias canteen -keyalg RSA -keysize 2048 -validity 10000

CANTEEN_KEYSTORE=/path/outside/the/repo/canteen-release.jks \
CANTEEN_KEYSTORE_PASSWORD='the password you chose' \
  ./gradlew assembleRelease

The keystore and its password never go into the repository. The build file reads both from the environment, and .gitignore excludes .jks and .keystore along with local.properties, which holds the path to the SDK on your own machine (Chapter 39). A key in a public repository is worse than no key: anyone can sign an app that claims to be yours.

Keep the keystore and write down where it is. An app updated with a different key is a different app to Android, and there is no way back.

What the two APKs are

Built and inspected on 30 September 2026 with the SDK's own tools:

DebugRelease
Size3.1 MB2.4 MB
Signed byC=US, O=Android, CN=Android DebugCN=Canteen Pre-order, OU=TY BSc CS, O=College, ...
Packageexample.college.canteen, version 1.0the same
Asks forandroid.permission.INTERNETthe same
Fortesting on your own phoneshanding in, and installing on students' phones

The tools that say so are aapt2 dump badging, which prints the package, the versions and the permissions, and apksigner verify --print-certs, which prints who signed it. Run both on your own APK before you hand it in, and put the output in your report: it is the difference between "we built an app" and evidence.

One honest detail the tools show: besides INTERNET, the APK declares a permission the AndroidX libraries add for their own internal use, DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION. A reader who runs the command will see it, so the book names it rather than pretending the list is one line long.

Putting it on a phone

Two ways, and both are worth knowing on the day:

  1. By cable, with the SDK's own tool: turn on developer options and USB debugging on the phone, then adb install app-debug.apk.
  2. By file, which is what students in the trial did: put the APK where they can download it, and let them install it. Android will warn that it is not from a store, and they must allow it once. Say so on the card at the counter, or half of them will stop there.
munotes.in401

APK Build: Putting the Application on an Android Phone

The worked team did the second, from a link on the same server, and two members installed it by cable to test first (Chapter 18).

What the examiner will ask

  • "Show me the app running." Have the APK on a phone before the examination, and check it against the server you will demonstrate, not the one on your laptop.
  • "What is in the APK?" The two commands above.
  • "Why a WebView and not a native app?" ADR-1, in one sentence: the pages already work on a phone, no requirement needs the phone's own hardware, and the team's hours were better spent on the ordering rules.
  • "What happens with no network?" The app shows its own message and a Try again button. It does not pretend to work.

Do this for your project

  1. Decide, from your requirements, whether you need an app at all, and write the decision down.
  2. If a WebView app is right, keep it to one screen and keep every rule on the server.
  3. Put the server's address in the build file, not in the code.
  4. Allow plain HTTP for one address only, and delete that permission when you have HTTPS.
  5. Make a release key, keep it outside the repository, and never commit a keystore or a password.
  6. Inspect your own APK with aapt2 dump badging and apksigner verify, and put the output in your report.
  7. Install it on a real phone, on the network you will demonstrate on, days before the examination.

Mistakes that cost marks

A debug APK handed in as the deliverable, signed by a key everyone has.

A keystore in the repository, with its password in the build file.

usesCleartextTraffic="true" for everything, which turns off a protection for every address the app touches.

The server's address typed into the Kotlin, so a new server means editing code.

An app built the night before, tested against a laptop that will not be in the examination hall.

"We made an Android app" with no APK, no package name and no signature to show.

Quick revision

  • A WebView app shows your own pages; the rules stay on the server, so a page change needs no new APK.
  • ADR-1 chose it and recorded the cost: it works only with the server.
  • minSdk from what your users have; the server's address in the build file.
  • Plain HTTP needs a network security configuration for that one address; delete it when HTTPS arrives.
  • Debug key for testing, your own key for the release; the keystore never in the repository.
  • Prove it with aapt2 dump badging and apksigner verify --print-certs.
munotes.in402

APK Build: Putting the Application on an Android Phone

Questions you must be able to answer

1. What is a WebView app, and when is it the right choice? An app whose whole interface is the platform's browser engine showing web pages, here the project's own. It is right when the pages already work on a phone and no requirement needs the phone's own hardware, because it costs one screen of code instead of a second implementation of everything.

2. What does the WebView app not give you? Anything offline, and anything that needs the phone itself: the camera, notifications, contacts, background work. It also depends entirely on the server being reachable, which is why its one screen of its own is the message shown when it is not.

3. Why is the server's address in the build file rather than in the code? So that building for a different server is a different build of the same program, with nothing edited by hand. It reaches the code as a generated constant.

4. Why does the app need a network security configuration? Because modern Android forbids plain HTTP by default, and the trial's server has no certificate. The configuration allows it for that one address and keeps the ban for everything else, and it is deleted when the server gets a name and HTTPS.

5. What is the difference between a debug APK and a release APK? The debug one is signed with a key that every Android installation shares, which identifies nobody and is for testing. The release one is signed with a key you generate and keep, which identifies your app: Android will not accept an update signed with a different key.

6. How do you prove what your APK contains? With the SDK's own tools: aapt2 dump badging prints the package name, the versions and the permissions requested, and apksigner verify --print-certs prints the certificate that signed it. Both outputs belong in the report.

Contents This chapter on its own page

munotes.in403

Chapter Sixty

Server Configuration: Linux, Nginx, systemd and HTTPS

Syllabus topic Module 2, "Deployment: ... Server configuration (if applicable)".

In one line

Configuring the server means making the machine run the application without anybody logged in: a user that owns only the application, a service that starts at boot and restarts if it stops, Nginx as the one program facing the network, a firewall, and, as soon as there is a public name, HTTPS.

In the wording to use when asked: server configuration provisions the runtime environment for a deployed application: a dedicated unprivileged service account, a process supervisor that starts the service at boot and restarts it on failure, a reverse proxy terminating client connections and forwarding to the application on the loopback interface, host firewall rules, and TLS with a certificate from a trusted authority, renewed automatically.

What goes where

WhereWhy
The code/opt/canteennot in anybody's home directory
The settings, with the database password/opt/canteen/.env, readable only by its usera password in a world-readable file is public (S13)
The service definitioncanteen.service, in /etc/systemd/system/so the system starts it
The Nginx sitecanteen, in /etc/nginx/sites-available/ and linked into sites-enabledUbuntu's own convention
The logsthe system journalone place, rotated by the system

And a user of its own, canteen, who owns the application's folder and nothing else and cannot sign in. If the application is ever made to run something it should not, it runs as a user who can do nothing.

That table is in the repository too, as deploy/README.md, beside the two files it describes, so that whoever installs them does not have to find this chapter:

# deploy/

What goes on the server, and where. Chapter 60 walks through it.

| File | Its place on the server |
| --- | --- |
| `canteen.service` | `/etc/systemd/system/canteen.service` |
| `canteen.nginx.conf` | `/etc/nginx/sites-available/canteen`, linked into `sites-enabled` |

The application itself lives in `/opt/canteen`, owned by a user named
`canteen` who owns nothing else and cannot sign in. Its settings are in
`/opt/canteen/.env`, readable only by that user, and are never in this
repository (see `.gitignore`).

Nothing here holds a password, an address or a certificate: the unit
reads the settings file, and the Nginx configuration answers on any name
the machine has. When the server has a public name, certbot rewrites
the Nginx file to serve HTTPS on 443 and to redirect port 80 to it.

A folder that needs explaining gets a note in it (Chapter 69). The last paragraph is the one that saves a worried half hour: nothing in deploy/ holds a password, an address or a certificate, so both files are committed and read by anybody.

The service

# /etc/systemd/system/canteen.service
#
# Runs the canteen application as a service: started at boot,
# restarted if it stops, and logged by the system's own
# journal. NFR-10 asks for a restart within 10 seconds.
[Unit]
Description=Canteen Pre-order
# Start after the network and the database are up.
After=network.target mysql.service
Wants=mysql.service

[Service]
Type=simple
User=canteen
Group=canteen
WorkingDirectory=/opt/canteen
# The settings, including the database password, are in a
# file only this user can read (chmod 600), never in this
# unit, which is world readable.
EnvironmentFile=/opt/canteen/.env
ExecStart=/usr/bin/node /opt/canteen/src/server.js
# Stop cleanly: server.js closes the pool on SIGTERM.
KillSignal=SIGTERM
TimeoutStopSec=15
Restart=always
RestartSec=3
# The service needs nothing but its own folder.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/canteen
# Everything the application prints goes to the journal.
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
munotes.in404

Server Configuration: Linux, Nginx, systemd and HTTPS

Read the lines that matter:

  • After=network.target mysql.service: start after the network and the database. The application would survive starting first, because the pool opens connections when they are needed, but there is no reason to make it retry at every boot.
  • EnvironmentFile=/opt/canteen/.env: the settings, including the password, are read from a file only this user can read. Do not put the password in the unit: unit files are world readable.
  • Restart=always with RestartSec=3: if the application stops for any reason, it is started again three seconds later. That is NFR-10, which asks for a restart within ten seconds.
  • KillSignal=SIGTERM and TimeoutStopSec=15: the application is asked to stop, not killed. server.js stops taking requests, lets the ones in progress finish and closes the pool (Chapter 42); fifteen seconds is more than its own ten-second limit.
  • ProtectSystem=strict, ProtectHome, PrivateTmp, NoNewPrivileges: the service can write only to /opt/canteen. These four lines cost nothing and mean that a mistake in the application cannot touch the rest of the machine.

The commands that use it, which cannot be run inside a container, because a container has no init system: this book's lab is a container, so these are printed and not run here. On the lab desktop they are the whole of it:

sudo cp deploy/canteen.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now canteen
systemctl status canteen
sudo journalctl -u canteen -f

enable --now does the two things this is all for: start it now, and start it at every boot. journalctl -u canteen -f is how the day's requests are read after the break (Chapter 42's log lines land there).

Proving NFR-10 is one command and one check: sudo systemctl kill canteen and then, a few seconds later, systemctl status canteen shows it running again, with the restart in the journal. That is the evidence for the test report (Chapter 55), and it takes ten seconds to produce.

Nginx in front

# /etc/nginx/sites-available/canteen
#
# Nginx is the only program facing the network. It serves the
# pages and passes /api requests to the application on
# 127.0.0.1:3000, which is not reachable from outside at all.
server {
    listen 80;
    listen [::]:80;
    server_name _;

    # The application says who each request came from by the
    # address Nginx reports, and the sign-in limiter counts by
    # it (NFR-6), so it must be the real client's address.
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host;

    # A request body over 10 kB is refused by the application
    # itself; Nginx refuses anything much larger first.
    client_max_body_size 64k;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_read_timeout 30s;
    }
}
munotes.in405

Server Configuration: Linux, Nginx, systemd and HTTPS

Three reasons for it, and only the third is about speed:

  1. One program faces the network. The application listens on 127.0.0.1 and cannot be reached from outside at all (S14, Chapter 31). Nginx is written for the network and has been read by more people than anything the team wrote.
  2. It is where HTTPS goes. The certificate, the redirect from port 80, and the protocols are Nginx's business, and the application never changes.
  3. It serves what it is good at: many connections at once, slow clients, and files.

proxy_set_header X-Forwarded-For is the line that matters most for this application, and the next section is why.

The setting that can lock a student out

The application's limit on failed sign-ins counts by the address the request came from and the email tried (NFR-6, Chapter 46). Behind a proxy, every request appears to come from the proxy unless the application is told to trust the proxy's own account of who the client was. So:

  • with TRUST_PROXY unset, every request looks like 127.0.0.1, and five wrong passwords from anywhere lock that student out of their own account;
  • with TRUST_PROXY=loopback, the application believes the address Nginx reports, and only the attacker is stopped.

Here it is, with Nginx really running in front of the application:

$ cd ~/canteen-preorder
$ sudo cp deploy/canteen.nginx.conf /etc/nginx/sites-available/canteen
$ sudo ln -sf /etc/nginx/sites-available/canteen /etc/nginx/sites-enabled/canteen
$ sudo rm -f /etc/nginx/sites-enabled/default
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo nginx
$ npm start > server.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s localhost/api/health; echo
{"status":"ok","database":"ok"}
$ wrong() { curl -s -o /dev/null -w "%{http_code} " -H "X-Forwarded-For: $1" \
>   -H 'Content-Type: application/json' \
>   -d '{"email":"priya@college.example","password":"guess"}' \
>   localhost/api/auth/login; }
$ right() { curl -s -o /dev/null -w "%{http_code}\n" -H "X-Forwarded-For: $1" \
>   -H 'Content-Type: application/json' \
>   -d '{"email":"priya@college.example","password":"canteen-demo"}' \
>   localhost/api/auth/login; }
$ for i in 1 2 3 4 5; do wrong 10.0.0.5; done; echo
401 401 401 401 401
$ echo -n "Priya, from her own phone: "; right 10.0.0.9
Priya, from her own phone: 429
$ kill %1
$ TRUST_PROXY=loopback npm start > proxied.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
[1]-  Terminated              npm start > server.log 2>&1
$ for i in 1 2 3 4 5; do wrong 10.0.0.5; done; echo
401 401 401 401 401
$ echo -n "Priya, from her own phone: "; right 10.0.0.9
Priya, from her own phone: 200
munotes.in406

Server Configuration: Linux, Nginx, systemd and HTTPS

The five wrong answers are 401 each time, as they should be. Then: without the setting, Priya is refused 429 on her own phone with her own password, because the attacker's failures were counted against the proxy's address, which is also hers. With it, she signs in, 200, while the attacker stays blocked.

That is one line in .env, and the difference between a limiter that protects students and one that attacks them. The architecture document records it (Chapter 36, section 9.1), and it is the kind of thing that is found by asking "what does this setting actually do" rather than by testing the application's own code.

The firewall

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Allow SSH before enabling the firewall on a machine you reach over SSH, or you will lock yourself out; that is the classic and it happens to everybody once. Note what is not allowed: 3000, the application, and 3306, MySQL. Neither should ever be reachable from the network.

HTTPS, when there is a name

On the college's machine there is no public name, so there is no certificate and no HTTPS, and Chapter 31 records that as the trial's largest weakness. On a public server with a name (Chapter 58), it is three commands:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d canteen.example.edu
sudo systemctl status certbot.timer

certbot --nginx gets a certificate from Let's Encrypt, rewrites the Nginx file to serve HTTPS on 443, and adds a redirect from port 80. The timer renews the certificate before it expires, which is what makes the whole thing maintainable: a certificate that must be renewed by hand every ninety days is a certificate that will expire during a demonstration.

Then one line changes in .env:

COOKIE_SECURE=true

and the application marks the sign-in cookie Secure and sends the header that tells browsers to use HTTPS for the site in future (S12, Chapter 46). Its test already exists (Chapter 53), and the Android app's cleartext exception can be deleted (Chapter 59).

The order to do it in

  1. Create the user and the folder; put the code in /opt/canteen.
  2. Install Node.js and MySQL; create the database and its account (Chapters 39 and 44).
  3. Write .env, chmod 600, and run the database setup.
  4. Install the service; enable --now; check systemctl status.
  5. Install the Nginx site; nginx -t; reload; check the health endpoint through port 80.
  6. Set TRUST_PROXY=loopback and restart the service.
  7. Turn on the firewall, SSH first.
  8. When there is a name: certbot, then COOKIE_SECURE=true, then restart.
munotes.in407

Server Configuration: Linux, Nginx, systemd and HTTPS

Each step is checkable, and the worked team's notes from the practice deployment (Chapter 57) are exactly this list with the surprises written beside it.

Do this for your project

  1. Give the application a user of its own that cannot sign in and owns only its folder.
  2. Keep every secret in a settings file that only that user can read, never in the unit.
  3. Use the system's own supervisor: start at boot, restart on failure, log to the journal.
  4. Put a web server in front, and keep the application on the loopback address.
  5. Tell the application to trust the proxy, and prove what happens when it does not.
  6. Turn on the firewall, allowing SSH first, and never open the application's or the database's port.
  7. Get a certificate the moment you have a name, and check that renewal is automatic.

Mistakes that cost marks

npm start in a terminal as the deployment, dying at the next logout.

The database password in the unit file, which everyone on the machine can read.

The application listening on 0.0.0.0 with no proxy and no firewall.

A proxy with no X-Forwarded-For, or an application that does not trust it, so the sign-in limit punishes the wrong person.

ufw enable before allowing SSH, on a machine in a data centre.

A certificate renewed by hand, which expires on the day it matters.

Quick revision

  • A user of its own, code in /opt/canteen, settings in .env readable only by that user.
  • systemd: EnvironmentFile, Restart=always (NFR-10), SIGTERM and a stop timeout, and the Protect lines.
  • enable --now starts it and enables it at boot; journalctl -u canteen -f reads it.
  • Nginx faces the network; the application stays on 127.0.0.1; X-Forwarded-For plus TRUST_PROXY=loopback, or the limiter blocks the victim.
  • Firewall: SSH first, then 80 and 443; never 3000 or 3306.
  • certbot for the certificate and the redirect; then COOKIE_SECURE=true.

Questions you must be able to answer

1. Why run the application as a service rather than from a terminal? Because a service is started by the system at boot, restarted if it stops, and logged in one place, and it survives every logout. A process started by hand ends when the terminal does, which for a canteen means the system is down until someone notices.

munotes.in408

Server Configuration: Linux, Nginx, systemd and HTTPS

2. Which lines of the unit meet NFR-10, and how would you prove it? Restart=always with RestartSec=3. It is proved by killing the service and checking a few seconds later that it is running again, with the restart recorded in the journal.

3. Why does the application keep listening on 127.0.0.1 when Nginx is in front? So that nothing but Nginx can reach it. The application is then not on the network at all, and only one program, written for that job and read by far more people, faces the outside.

4. What goes wrong if the application does not trust the proxy? Every request appears to come from the proxy, so the limit on failed sign-ins counts them all against one address. Five wrong passwords typed anywhere then lock that student out of their own account, which turns a protection into an attack.

5. Why must the database password be in the settings file rather than the unit? Because unit files are readable by everyone on the machine, while the settings file can be readable only by the service's own user.

6. What does certbot do, and why does its timer matter? It obtains a certificate from Let's Encrypt, rewrites the web server's configuration to serve HTTPS and redirect plain HTTP to it, and installs a timer that renews the certificate automatically. Let's Encrypt certificates are short lived, so a project without automatic renewal has one that will expire, probably at the worst moment.

Contents This chapter on its own page

munotes.in409

Chapter Sixty-One

Version Control Using GitHub, Part 1: the Repository, Commits and the Remote

Syllabus topic Module 2, "Deployment: ... Version control using GitHub (Mandatory)", first part.

In one line

Git records what changed, when, and by whom, one commit at a time, and GitHub keeps a copy of that record where the whole team and the examiner can see it; MU makes it mandatory because the history is the only evidence of how a project was really built.

In the wording to use when asked: version control records successive states of a project as commits, each identified by a hash and carrying an author, a timestamp and a message; a distributed system such as Git gives every clone the full history, and a hosted remote such as GitHub provides the shared copy, the issue tracker and the review tools a team works through.

Why MU makes it mandatory

The syllabus says "Version control using GitHub (Mandatory)", and it is the only item in Module 2 marked so (Chapter 38). Two reasons, one for you and one for the examiner:

  • For you: a record you can go back to. Every student has deleted something they needed, or broken something that worked on Tuesday. With a history, both are a command away.
  • For the examiner: evidence. A repository shows who wrote what, and when, over fifteen weeks. It is the second of the three places a downloaded project fails (Chapter 1), and nothing can be added to it afterwards without showing.

What Git records

A commit is a snapshot of the whole project at one moment, plus who made it, when, and a message saying why. Commits are chained: each names its parent, so the chain is the history.

Three places a file can be:

PlaceWhat it meansHow it moves on
The working treethe files as they are on your diskgit add
The staging areawhat will go into the next commitgit commit
The repositorythe committed historygit push sends it to the remote

The staging area is the part beginners find strange, and it is the part worth using: it lets you commit one change even when you have made three, which is what makes a history readable.

Starting, and the first commits

Everything below is run here, in an empty folder, so the output is what a student sees:

$ rm -rf ~/first-repo; mkdir ~/first-repo && cd ~/first-repo
$ git init
Initialized empty Git repository in /home/student/first-repo/.git/
$ echo "# Canteen Pre-order" > README.md
$ git status --short
?? README.md
$ git add README.md
$ git status --short
A  README.md
$ git commit -m "Start the project with a README"
[main (root-commit) 2ff0254] Start the project with a README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
$ git --no-pager log --oneline
2ff0254 (HEAD -> main) Start the project with a README
munotes.in410

Version Control Using GitHub, Part 1: the Repository, Commits and the Remote

Read the two git status --short lines: before git add, the file is ??, unknown to Git; after it, A, ready to be committed. And git log --oneline shows the commit with the short form of its hash, which is how every commit is named for the rest of the project's life.

What a commit message is for

The message is read by your team, by your guide at the code review, and by you in six weeks. The rule that fits a mini project:

  • The first line says what changed, in the imperative: "Refuse an order for a closed slot", not "fixed stuff" and not "changes".
  • If why is not obvious, a blank line and then why. Not how: the diff already says how.
  • One commit, one change. Two changes in one commit cannot be undone separately, and cannot be reviewed separately.

Compare the two histories a guide sees:

A history that says nothingA history that says something
update, update2, fix, final, asdfTake the stock in one conditional update, Refuse a second order for the same slot, Show the counter's list without reloading

Seeing what changed

$ cd ~/first-repo
$ printf 'Order lunch before the break.\n' >> README.md
$ git --no-pager diff
diff --git a/README.md b/README.md
index 60f9834..4b219c5 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
 # Canteen Pre-order
+Order lunch before the break.
$ git add README.md
$ git commit -q -m "Say what the project is for"
$ git --no-pager log --oneline
7ed456d (HEAD -> main) Say what the project is for
2ff0254 Start the project with a README
$ git --no-pager show --stat HEAD | head -6
commit 7ed456d685b84c0788034e1b5ffd35447fa60935
Author: Aditi Kulkarni <aditi@college.example>
Date:   Tue Sep 29 10:30:17 2026 +0530

    Say what the project is for

git diff shows what is changed and not yet staged; git show shows what one commit changed, with who made it and when: that author line is the reason Chapter 39 sets your real name and email on every machine, because it is what a guide and an examiner read (Chapter 72). Read your own diff before every commit. It is the cheapest review there is, and it catches the debugging line you left in.

What must never be committed

Chapter 39 wrote the .gitignore before the first commit, and it is worth repeating why each line is there:

  • node_modules/: thousands of other people's files, which npm ci recreates exactly from the lock file.
  • .env: the database password. A secret committed once is in the history for ever, even if the next commit deletes it: anyone with the repository can read it out of the old commit. If it happens, change the password, do not only delete the file.
  • Build output and machine-specific files: the Android build folders and local.properties (Chapter 59), and the editors' own folders.
munotes.in411

Version Control Using GitHub, Part 1: the Repository, Commits and the Remote

Git shows the file is being ignored rather than forgotten:

$ cd ~/first-repo
$ printf 'node_modules/\n.env\n' > .gitignore
$ mkdir node_modules && touch node_modules/left-pad.js
$ printf 'DB_PASSWORD=not-in-the-repository\n' > .env
$ git status --short
?? .gitignore
$ git status --short --ignored | head -4
?? .gitignore
!! .env
!! node_modules/
$ git add .gitignore && git commit -q -m "Ignore installed packages and secrets"
$ git --no-pager log --oneline | head -3
9ba3dc5 Ignore installed packages and secrets
7ed456d Say what the project is for
2ff0254 Start the project with a README

The first git status shows only .gitignore: neither the packages nor the settings file is offered for committing at all.

The remote

A remote is a copy of the repository somewhere else, and for this paper it is GitHub. These are the commands, and they are the ones this book cannot run, because they talk to a real service:

git remote add origin https://github.com/YOUR-ACCOUNT/canteen-preorder.git
git branch -M main
git push -u origin main
git pull
  • remote add origin names the copy on GitHub origin, the usual name for the one you clone from.
  • branch -M main names the main line of development main. Chapter 39 set that as the default for new repositories, so it is usually already so.
  • push -u origin main sends your commits and remembers where they went, so later a bare git push is enough.
  • git pull brings down what your team has pushed. Pull before you start work, every time, and most of Chapter 62's conflicts never happen.

Signing in to GitHub

A password will not do: GitHub wants either a personal access token, which you paste when Git asks for a password, or an SSH key. For a semester project either is fine, and the rule for both is the same as for every other secret: it belongs on your machine, never in the repository.

What the repository should look like

The worked team's, at the end of the project, and what each part says to an examiner (Chapter 72):

What it shows
About 180 commits, by four people, spread over fifteen weeksthe work happened over the semester, and everyone did some
Messages that name what changedthe team knew what they were doing
docs/ from week 2, src/ from 11 Septemberthe design came before the code, as the plan said
Issues opened and closed, each linked to a committhe defects were tracked (Chapter 56)
No node_modules, no .envthe basics were understood
A README that starts with how to run itsomebody can use it without asking (Chapter 69)
munotes.in412

Version Control Using GitHub, Part 1: the Repository, Commits and the Remote

When something goes wrong

Four commands that save a project, in order of how often a student needs them:

git restore <file>                  # throw away my changes to this file
git restore --staged <file>         # unstage it, keep my changes
git commit --amend                  # fix the message of the commit I just made
git revert <commit>                 # a new commit that undoes an old one

git revert rather than deleting history. It adds a commit that undoes the change, so the record stays honest, which is what a history is for. Rewriting history that other people have pulled is the one way to make Git genuinely painful, and it is never needed in a mini project.

Do this for your project

  1. Create the repository on your first day, and commit the plan and the diagrams as you write them.
  2. Write .gitignore before the first commit, with node_modules and your settings file in it.
  3. Commit on the day you write the code, one change at a time, with a message in the imperative.
  4. Read your own diff before every commit.
  5. Pull before you start, push when you stop.
  6. Never commit a secret; if you do, change it, do not only delete it.
  7. Undo with revert, and leave the history alone.

Mistakes that cost marks

One commit at the end, which says the project was written at the end.

node_modules committed, so the repository is thousands of files of other people's code.

.env committed, with the database password in the history for ever.

Messages like update, which tell the guide nothing at the code review.

Everything committed by one member, in a group of four.

History rewritten to hide a mistake, which breaks everyone else's clone.

Quick revision

  • Working tree, staging area, repository; add, commit, push.
  • A commit: a snapshot, an author, a time, and a message: imperative first line, why if it is not obvious, one change.
  • .gitignore first: node_modules/, .env, build output.
  • A secret committed once is in the history: change it.
  • origin is the GitHub copy; push -u origin main, then git push; pull before you start.
  • Undo with restore, --amend, revert; do not rewrite shared history.

Questions you must be able to answer

1. Why does MU make GitHub mandatory for this paper? Because the history is the evidence of how the project was built: who wrote what and when, over the semester. It cannot be produced at the end, which is why a downloaded project fails there, and it is also what lets a team recover anything they break.

munotes.in413

Version Control Using GitHub, Part 1: the Repository, Commits and the Remote

2. What are the three places a change can be, and what moves it between them? The working tree, the files on disk; the staging area, what will go into the next commit, reached with git add; and the repository, the committed history, reached with git commit. git push copies commits to the remote.

3. What makes a good commit message? A first line in the imperative saying what changed, and, when the reason is not obvious, a blank line and the why. Each commit should be one change, so that it can be read, reviewed and undone on its own.

4. Why must .env never be committed, and what do you do if it is? Because it holds secrets, and anything committed stays in the history even after it is deleted, so the secret is readable by anyone with the repository. If it happens, change the secret itself, not only the file.

5. What is a remote, and what do push and pull do? A remote is a copy of the repository elsewhere, here on GitHub, usually named origin. push sends your commits to it; pull brings down the commits others have pushed. Pulling before starting work avoids most conflicts.

6. How should a mistake in the history be undone? With git revert, which adds a new commit undoing the old change, so that the record of what happened stays intact. Rewriting history that others have already pulled breaks their copies and is never necessary in a project this size.

Contents This chapter on its own page

munotes.in414

Chapter Sixty-Two

Version Control Using GitHub, Part 2: Branches, Pull Requests and Releases

Syllabus topic Module 2, "Deployment: ... Version control using GitHub (Mandatory)", second part.

In one line

A branch is a line of work that does not disturb the main one; a pull request is that work offered for someone to read before it joins; and a release is a named point in the history that says "this is the version we handed in".

In the wording to use when asked: a branch is a movable pointer to a commit, allowing parallel lines of development; merging integrates one line into another, and where both changed the same lines a conflict is raised for a human to resolve; a pull request is a hosted request to merge, with review and discussion attached; and a tag marks a specific commit, from which a release is published.

Why a team needs branches

Four people committing to one line of work get in each other's way within a day. The worked team used one branch per piece of work, named after it:

BranchWhoWhat
maineverybodyalways works; what is deployed
feature/order-transactionFarhanthe stock rule and its transaction
feature/counter-screenSnehathe counter's page
fix/31-stray-detailsSnehaissue 31 (Chapter 56)

Two conventions that cost nothing and save arguments: main always works, so nothing broken is merged into it; and the branch name says what it is for, with the issue's number when there is one.

Making a branch, and merging it

$ rm -rf ~/team-repo; mkdir ~/team-repo && cd ~/team-repo
$ git init -q && printf 'slots: 12:30 12:40 12:50 13:00\n' > rules.txt
$ git add rules.txt && git commit -q -m "Write down the pickup slots"
$ git switch -c feature/cutoff
Switched to a new branch 'feature/cutoff'
$ printf 'cut-off: 15 minutes before the slot\n' >> rules.txt
$ git commit -q -am "Add the cut-off rule"
$ git switch main
Switched to branch 'main'
$ cat rules.txt
slots: 12:30 12:40 12:50 13:00
$ git merge feature/cutoff -m "Merge the cut-off rule"
Updating 4b2a271..e3c3f51
Fast-forward (no commit created; -m option ignored)
 rules.txt | 1 +
 1 file changed, 1 insertion(+)
$ cat rules.txt
slots: 12:30 12:40 12:50 13:00
cut-off: 15 minutes before the slot
$ git --no-pager log --oneline --graph
* e3c3f51 (HEAD -> main, feature/cutoff) Add the cut-off rule
* 4b2a271 Write down the pickup slots

Read what happened: on main the file had one line, because the second commit was made on the branch; after the merge it has both. This merge was a fast forward, because main had not moved while the branch was being written: Git had nothing to combine and simply moved the pointer forward, which is why it says so and ignores the merge message it was given. That is the common case when one person's work is merged promptly, and the graph shows one straight line rather than a fork and a join.

munotes.in415

Version Control Using GitHub, Part 2: Branches, Pull Requests and Releases

A conflict, made and resolved

A conflict happens when two branches change the same lines. It is not a failure, and it is not rare: it is Git refusing to guess.

$ cd ~/team-repo
$ git switch -c feature/more-slots
Switched to a new branch 'feature/more-slots'
$ sed -i 's/^slots: .*/slots: 12:30 12:40 12:50 13:00 13:10/' rules.txt
$ git commit -q -am "Offer a fifth pickup slot"
$ git switch main
Switched to branch 'main'
$ sed -i 's/^slots: .*/slots: 12:30 12:45 13:00/' rules.txt
$ git commit -q -am "Reduce to three pickup slots"
$ git merge feature/more-slots
Auto-merging rules.txt
CONFLICT (content): Merge conflict in rules.txt
Automatic merge failed; fix conflicts and then commit the result.
$ cat rules.txt
<<<<<<< HEAD
slots: 12:30 12:45 13:00
=======
slots: 12:30 12:40 12:50 13:00 13:10
>>>>>>> feature/more-slots
cut-off: 15 minutes before the slot

Git has written both versions into the file, between markers: everything between <<<<<<< and ======= is what main says, and everything to >>>>>>> is what the branch says. Nothing is lost, and nothing is decided.

Resolving it is editing the file to what it should be, and committing:

$ cd ~/team-repo
$ printf 'slots: 12:30 12:40 12:50 13:00 13:10\ncut-off: 15 minutes before the slot\n' > rules.txt
$ git add rules.txt
$ git commit -q -m "Merge: keep the five slots the owner asked for"
$ cat rules.txt
slots: 12:30 12:40 12:50 13:00 13:10
cut-off: 15 minutes before the slot
$ git --no-pager log --oneline --graph | head -6
*   e322b14 Merge: keep the five slots the owner asked for
|\
| * e9fe3cc Offer a fifth pickup slot
* | 464d646 Reduce to three pickup slots
|/
* e3c3f51 Add the cut-off rule
$ git branch --merged main | tr -d ' ' | tr '\n' ' '
feature/cutoff feature/more-slots *main

The decision is the team's, not Git's. Here the owner had asked for five slots, so the branch's version won; the resolution's message says so, which is what makes the history readable later. And git branch --merged lists what has been merged and can be deleted: branches are cheap, and leaving a dozen stale ones is untidy.

Two habits that prevent most conflicts: pull before you start, and keep a branch short. A branch that lives for two weeks will conflict; one that lives for a day rarely does.

Pull requests

A pull request is a branch offered for merging, on GitHub, with the diff, the discussion and the checks in one place. A book cannot open one, so here is what the worked team did and what each part is for.

munotes.in416

Version Control Using GitHub, Part 2: Branches, Pull Requests and Releases

The one command a student runs locally is to push the branch:

git push -u origin feature/order-transaction

GitHub then offers to open the pull request. The rest is on the website:

PartWhat the team put there
Titlewhat the change does: "Take an order's stock in one conditional update"
Descriptionwhy, and what to look at; "Closes #17" to link the issue
Reviewerthe member who did not write it
The diffread line by line by the reviewer (Chapter 49's checklist)
Commentsquestions and suggestions, answered or taken
Mergeonly after approval, and only when the branch works

The worked pull request, the one Prof. Iyer looked at in the code review (Chapter 49): Farhan's ordering change, reviewed by Rohan, three comments, two taken.

  • "What happens if the second item is short?" Answered, and a test was added: the one that proves the stock is untouched (Chapter 45).
  • "refusal() reads the item again inside the transaction. Is that needed?" Yes: it is what turns "no rows changed" into the right message, sold out or unavailable, and the comment above it now says so.
  • "Could place() be shorter?" Not taken, and the reason was recorded: splitting the transaction across two functions would make it easy to end it in the wrong place.

A review is a conversation, not a verdict, and its record in the pull request is evidence for the examiner that the team read each other's code.

Protecting main

GitHub can require that main is changed only through a pull request, and that someone other than the author approves it. For a team of four it takes a minute to switch on and it makes the rule real instead of merely agreed.

Releases

When there is a version worth naming, tag it:

$ cd ~/team-repo
$ git tag -a v1.0 -m "The version submitted for Mini Project I"
$ git --no-pager tag
v1.0
$ git --no-pager show v1.0 --stat | head -5
tag v1.0
Tagger: Aditi Kulkarni <aditi@college.example>
Date:   Tue Sep 29 10:30:17 2026 +0530

The version submitted for Mini Project I
git push origin v1.0

A tag is a name for one commit, and it does not move. On GitHub a tag can be published as a release, with notes and files attached; for this paper the useful attachment is the signed APK (Chapter 59), so that an examiner can download exactly the file that was demonstrated.

What to tag in a mini project: the version shown at each increment review, and the version submitted. Three tags are plenty, and v1.0 on the submitted commit is the one that matters: it says precisely which state of the code the report describes.

munotes.in417

Version Control Using GitHub, Part 2: Branches, Pull Requests and Releases

What the examiner sees

Chapter 72 is about the repository as a deliverable, and branches, pull requests and releases are most of what it shows:

  • branches that were merged and deleted, which means work was organised;
  • pull requests with review comments, which means the team read each other's code;
  • issues closed by commits, which means defects were tracked (Chapter 56);
  • a tag on the submitted version, which means the report and the code agree.

Do this for your project

  1. Keep main working, and do every piece of work on its own short-lived branch.
  2. Name branches after the work, with the issue number when there is one.
  3. Pull before you start; merge promptly; delete merged branches.
  4. Open a pull request for every change, and have someone else read it.
  5. Resolve conflicts by deciding, and say in the merge message what you decided.
  6. Protect main so that the rule is enforced, not merely agreed.
  7. Tag the version you submit, and attach the APK to the release.

Mistakes that cost marks

Everyone committing to main, and a broken main on the day of the demonstration.

A branch that lives for a month, whose merge is a day of conflicts.

Conflict markers committed, so <<<<<<< appears in the submitted code.

Pull requests merged by their own author with no review, which wastes the practice and the evidence.

No tag, so nobody can tell which commit the report describes.

--force pushes to shared branches, which quietly destroy other people's work.

Quick revision

  • A branch per piece of work, named after it; main always works.
  • git switch -c makes one; git merge joins it; a fast forward when main has not moved.
  • A conflict is Git refusing to guess: both versions are written between markers, and you decide.
  • Pull requests: push the branch, then title, description with "Closes #n", a reviewer, the diff, comments, merge on approval; protect main.
  • Tag the submitted version (git tag -a v1.0), publish it as a release, attach the APK.
  • Prevent conflicts: pull before you start, keep branches short.

Questions you must be able to answer

1. Why does a team work on branches rather than all on main? So that unfinished work never breaks the line everyone shares and everyone deploys. Each person's work is separate until it is finished and reviewed, and main can always be demonstrated.

2. What is a merge conflict, and whose job is it to resolve? It happens when two branches change the same lines, and Git will not choose between them. It writes both versions into the file between markers, and a person decides what the file should say, then commits the result.

munotes.in418

Version Control Using GitHub, Part 2: Branches, Pull Requests and Releases

3. What is a pull request, and what belongs in one? A request to merge a branch, on GitHub, with the diff and the discussion attached. It should carry a title saying what the change does, a description with why and a link to its issue, a reviewer who did not write it, and the review's comments and their answers.

4. Why protect the main branch? So that the team's rule, nothing merged without a review, is enforced by the tool rather than remembered by people at midnight before a deadline.

5. What is a tag, and which commits should be tagged in a mini project? A fixed name for one commit. Tag the version shown at each increment review and, most importantly, the version submitted, so that the report, the demonstration and the code can be shown to be the same thing.

6. Why are short-lived branches better than long ones? Because the longer a branch lives, the more the main line moves under it, and the more lines both have changed. A branch merged the day it is written rarely conflicts at all.

Contents This chapter on its own page

munotes.in419

Chapter Sixty-Three

Basic Load Testing

Syllabus topic Module 2, "Performance & Security Testing: Basic load testing".

In one line

A load test answers one question, "does it still answer quickly enough when everyone uses it at once", by making many requests at the same time and reading the distribution of the answers, not their average.

In the wording to use when asked: load testing measures a system's behaviour under a defined concurrent workload, reporting throughput, latency percentiles and error rate, in order to verify a performance requirement; results are meaningful only with the environment, the scenario and the data stated, since they depend on the hardware, the network and what the system is asked to do.

What NFR-1 asks, and where it came from

NFR-1. With 100 students ordering in the same minute on the lab server, 95 per cent of menu and order requests are answered within 1 second, and none fails.

Chapter 6 estimated the real peak: about 212 students a day, half of them ordering in the fifteen minutes before the first cut-off, which is about 7 orders a minute, some 70 requests. The requirement is set at 100 students in a minute, about fourteen times that, because an estimate made from five days of watching a queue deserves a wide margin.

Notice the three parts of a requirement worth testing: how many at once, how quickly, and how often (95 per cent, not on average). An average hides the worst experience; percentiles are what users feel.

Two tools, and what each is for

  • Apache Bench, ab, which Ubuntu installs with apache2-utils. It fires many requests at one address and reports the distribution. It is the right tool for "how fast is the menu", and it cannot sign in, place an order, or do anything a student really does.
  • The project's own script, npm run load, which does what a student does: signs in, reads the menu and the slots, places an order, looks at the orders. It is the right tool for the requirement, because the requirement is about students.

Use the first to find where the time goes, and the second to judge the requirement.

Apache Bench on the menu

$ cd ~/canteen-preorder
$ npm start > load.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ ab -n 500 -c 20 -q http://localhost:3000/api/menu 2>&1 \
>   | grep -E 'Requests per second|Time per request|Failed|50%|95%|99%|longest'
Failed requests:        0
Requests per second:    1411.51 [#/sec] (mean)
Time per request:       14.169 [ms] (mean)
Time per request:       0.708 [ms] (mean, across all concurrent requests)
  50%     10
  95%     25
  99%     55
 100%     60 (longest request)

Read it as three facts. Requests per second is throughput: how many the server got through. Time per request comes in two forms, and the one that matters to a user is the first, the time one request took while twenty were in flight. And the percentile table is the distribution: half the requests finished by the 50 per cent line, 95 per cent by the 95 per cent line.

munotes.in420

Basic Load Testing

Failed requests must be zero. A fast average with failures is not a pass; it is a system that drops work under load.

The whole visit, which is what the requirement is about

ab cannot place an order, so the project has its own script (Chapter 42). It is printed here in full, because a load test nobody can read is a number nobody can trust:

'use strict';

// npm run load        (with the server already running)
// The lunch rush of NFR-1: 100 students each sign in, read the
// menu and the slots, place an order and look at their orders,
// all within one minute. It reports how many requests failed,
// and how quickly the menu and order requests were answered,
// against the requirement: 95 per cent within 1 second, and
// none failing.
//
//   BASE_URL=http://127.0.0.1:3000 STUDENTS=100 SECONDS=60 \
//     npm run load
//
// It raises today's stock through the owner's demonstration
// account and registers its own students, so run it against a
// freshly set-up copy (npm run db:setup), never against the
// canteen's real data.

const BASE = process.env.BASE_URL || 'http://127.0.0.1:3000';
const STUDENTS = Number(process.env.STUDENTS || 100);
const SECONDS = Number(process.env.SECONDS || 60);
const SLOTS = ['12:30', '12:40', '12:50', '13:00'];
const PASSWORD = 'lunch-rush-practice'; // not a common password
const LIMIT_MS = 1000;

// A client that keeps its sign-in cookie, as a browser does,
// and times every request it makes.
function client(timings) {
  let cookie = '';
  return async function send(kind, method, path, body) {
    const headers = cookie ? { Cookie: cookie } : {};
    if (body !== undefined) {
      headers['Content-Type'] = 'application/json';
    }
    const start = performance.now();
    let status = 0;
    let data = null;
    try {
      const res = await fetch(BASE + path, { method, headers,
        body: body === undefined ? undefined : JSON.stringify(body) });
      status = res.status;
      const set = res.headers.get('set-cookie');
      if (set) cookie = set.split(';')[0];
      data = await res.json().catch(() => null);
    } catch {
      status = 0; // the server could not be reached at all
    }
    if (timings) {
      timings.push({ kind, status, ms: performance.now() - start });
    }
    return { status, data };
  };
}

// Not measured: the owner makes sure nothing sells out, and
// the students' accounts exist before the rush begins.
async function prepare() {
  const owner = client(null);
  await owner('setup', 'POST', '/api/auth/login',
    { email: 'owner@college.example', password: 'canteen-demo' });
  const { data } = await owner('setup', 'GET', '/api/menu');
  const items = data.items.filter((item) => item.isAvailable);
  for (const item of items) {
    await owner('setup', 'PUT', `/api/menu/${item.id}/stock`,
      { stockLeft: 1000 });
  }
  for (let i = 1; i <= STUDENTS; i += 1) {
    const n = String(i).padStart(3, '0');
    await client(null)('setup', 'POST', '/api/auth/register', {
      name: 'Rush Student', email: `rush.${n}@college.example`,
      password: PASSWORD,
    });
  }
  return items;
}

// One student's visit, as the pages make it.
async function visit(i, items, timings) {
  const n = String(i).padStart(3, '0');
  const send = client(timings);
  await send('sign in', 'POST', '/api/auth/login',
    { email: `rush.${n}@college.example`, password: PASSWORD });
  await send('menu', 'GET', '/api/menu');
  await send('slots', 'GET', '/api/slots');
  await send('order', 'POST', '/api/orders', {
    slot: SLOTS[i % SLOTS.length],
    items: [{ menuItemId: items[i % items.length].id, quantity: 1 }],
  });
  await send('my orders', 'GET', '/api/orders/mine');
}

const EXPECTED = { 'sign in': 200, menu: 200, slots: 200,
  order: 201, 'my orders': 200 };

function percentile(sorted, p) {
  return sorted[Math.max(0, Math.ceil((p / 100) * sorted.length) - 1)];
}

async function main() {
  const items = await prepare();
  const timings = [];
  const gap = (SECONDS * 1000) / STUDENTS;
  console.info(`${STUDENTS} students in ${SECONDS} seconds, `
    + `one every ${gap.toFixed(0)} ms, against ${BASE}`);
  // Each student starts at their own moment across the minute.
  await Promise.all(Array.from({ length: STUDENTS }, (_, k) =>
    new Promise((resolve) => {
      setTimeout(() => {
        visit(k + 1, items, timings).then(resolve);
      }, k * gap);
    })));

  const failed = timings.filter((t) => t.status !== EXPECTED[t.kind]);
  const measured = timings
    .filter((t) => t.kind === 'menu' || t.kind === 'order')
    .map((t) => t.ms).sort((a, b) => a - b);
  const quick = measured.filter((ms) => ms <= LIMIT_MS).length;
  const share = (100 * quick) / measured.length;
  console.info(`Requests: ${timings.length}, not as expected: `
    + `${failed.length}`);
  console.info(`Menu and order requests: ${measured.length}, `
    + `answered within 1 second: ${quick} `
    + `(${share.toFixed(1)} per cent)`);
  console.info('Their times in ms: median '
    + `${percentile(measured, 50).toFixed(0)}, 95th percentile `
    + `${percentile(measured, 95).toFixed(0)}, slowest `
    + `${measured[measured.length - 1].toFixed(0)}`);
  // Every kind of request, so that the slow one stands out.
  for (const kind of Object.keys(EXPECTED)) {
    const ms = timings.filter((t) => t.kind === kind)
      .map((t) => t.ms).sort((x, y) => x - y);
    console.info(`  ${kind.padEnd(9)}  median `
      + `${percentile(ms, 50).toFixed(0).padStart(4)} ms, 95th `
      + `percentile ${percentile(ms, 95).toFixed(0).padStart(4)} ms`);
  }
  const met = share >= 95 && failed.length === 0;
  console.info(met ? 'NFR-1 is met.' : 'NFR-1 is NOT met.');
  for (const t of failed.slice(0, 5)) {
    console.info(`  ${t.kind} answered ${t.status}`);
  }
  if (!met) process.exitCode = 1;
}

main().catch((err) => {
  console.error('Load test failed:', err.message);
  process.exitCode = 1;
});
munotes.in421

Basic Load Testing

Four things in it are worth copying into your own:

  • It does what the pages do, in the same order and with the same requests, so the numbers are about the system as used.
  • The students are spread across the minute, each starting at its own moment, rather than all in the same instant: that is a rush, not a thunderclap, and it is what NFR-1 describes.
  • It separates preparation from measurement. Registering a hundred accounts and setting the stock are not timed; the requirement is about ordering.
  • It judges the requirement itself and exits non-zero when it is not met, so it can be run by anything that runs tests.
munotes.in422

Basic Load Testing

$ cd ~/canteen-preorder
$ npm run db:setup > /dev/null
$ STUDENTS=100 SECONDS=60 npm run load 2>&1 | tail -10
100 students in 60 seconds, one every 600 ms, against http://127.0.0.1:3000
Requests: 500, not as expected: 0
Menu and order requests: 200, answered within 1 second: 200 (100.0 per cent)
Their times in ms: median 3, 95th percentile 6, slowest 21
  sign in    median  188 ms, 95th percentile  217 ms
  menu       median    1 ms, 95th percentile    3 ms
  slots      median    1 ms, 95th percentile    2 ms
  order      median    4 ms, 95th percentile    8 ms
  my orders  median    1 ms, 95th percentile    3 ms
NFR-1 is met.

That is NFR-1, measured: a hundred students in a minute, every request answered as expected, and the share of menu and order requests inside one second.

Where the time goes

The per-kind lines are the useful part of that report. Sign-in is by far the slowest, and it should be: it is one scrypt hash, which is deliberately expensive (Chapter 46). Everything else is a query or two against indexed columns (Chapter 44).

That tells you where to look if a load test ever fails: not at the code you wrote last, but at the slowest line of the report. And it tells you what not to do: making the password hash cheaper would speed up sign-in and weaken every account.

Reading a load test honestly

The machine matters. These numbers come from a container on a laptop, not from the lab desktop. A report that says "95 per cent within 1 second" without saying on what, with what data, and doing what, cannot be checked by anybody. The worked team's report says all four (Chapter 55).

The database matters. A load test against an empty database is easy; the same test with a term's orders in it is the one that finds a missing index. The worked team ran theirs after a day of the trial, with real rows in it.

A test on the same machine as the server measures the server and the client together and never sees the network. It is still worth doing, because it finds the slow query; it is not a claim about what students on Wi-Fi will feel.

munotes.in423

Basic Load Testing

Averages lie. If one request in twenty takes four seconds, the average is fine and one student in twenty thinks the system is broken. That is why the requirement is written as a percentile.

When a load test fails

In order, because this order is cheapest first:

  1. Read the slowest kind of request. One kind is nearly always most of it.
  2. Look for a query with no index, or one run in a loop: the N + 1 problem of Chapter 44 shows up under load long before it shows up in use.
  3. Count the round trips per request. Fewer statements beats a faster database.
  4. Check the connection pool: ten connections with a hundred students in flight is not a bottleneck here, but a pool of one would be.
  5. Only then think about more machines, which for a canteen is never the answer.

Do this for your project

  1. Write your performance requirement with a number of users, a time and a percentile.
  2. Test the journey your users actually make, not one address.
  3. Spread the arrivals over the period the requirement names.
  4. Report the environment, the data and the scenario beside the numbers.
  5. Read the percentiles, not the average, and require zero failures.
  6. When it fails, find the slowest kind of request before changing anything.
  7. Keep the test in the repository so that it can be run again after a change.

Mistakes that cost marks

"It is fast" with no number, no load and no machine.

A load test of the front page, which signs nobody in and orders nothing.

All the requests at the same instant, which measures a queue rather than a rush.

The average quoted and the slow tail hidden.

Failures ignored because the timings looked good.

A load test against an empty database, which is the one case a real system never meets.

Quick revision

  • NFR-1: 100 students in a minute, 95 per cent within 1 second, none failing, from an estimate of about 7 orders a minute with a wide margin.
  • ab for one address and the distribution; the project's own script for the whole visit.
  • Spread the arrivals; do not time the preparation; judge the requirement in the script.
  • Report the machine, the data and the scenario with the numbers.
  • Percentiles, not averages; zero failures.
  • If it fails: the slowest kind first, then indexes, then round trips, then the pool.

Questions you must be able to answer

1. What does a load test measure, and why are percentiles used rather than averages? It measures how a system behaves when many users are using it at once: throughput, how long the answers take, and how many fail. Percentiles are used because an average hides the worst cases, and it is the slowest requests that people notice and complain about.

munotes.in424

Basic Load Testing

2. Why is Apache Bench not enough to test NFR-1? Because it fires requests at a single address and cannot sign in or place an order, while the requirement is about students doing a whole visit: signing in, reading the menu and the slots, ordering, and looking at their orders.

3. Why does the load script spread its students across the minute? Because that is what the requirement describes, and what really happens: students arrive through the fifteen minutes before a cut-off. Sending every request in the same instant measures something else, and usually something worse than reality.

4. Why is signing in the slowest request, and should it be made faster? Because it computes an scrypt hash, which is deliberately slow so that stolen password hashes cannot be tried quickly. It should not be made faster: the cost is the protection, and it is paid once per sign-in rather than once per request.

5. What must be stated alongside load-test numbers? The machine they were measured on, the data in the database, the scenario the test performed, and how many users at what rate. Without those, the numbers cannot be compared or repeated.

6. A load test fails at 100 users. What do you look at first? The slowest kind of request in the report, because one kind is usually most of the time; then whether its queries use indexes and how many statements it makes per request. Adding machines is the last thing to consider, and for a system this size, never the answer.

Contents This chapter on its own page

munotes.in425

Chapter Sixty-Four

Input Validation Checks

Syllabus topic Module 2, "Performance & Security Testing: Input validation checks".

In one line

An input validation check is testing what the system does with everything a user could send, not what it does when used properly: wrong types, missing fields, empty values, enormous values, values one past a limit, and values that look like attacks, at every address that takes input, with nothing answering with a server error.

In the wording to use when asked: input validation testing is a systematic exercise of every input to a system with values outside its expected domain, verifying that each is rejected at the correct boundary with a defined error response, that no input causes an unhandled failure, and that inputs are neither silently coerced nor stored in a form that changes their meaning.

Why it is a security check and not only a testing one

MU lists it under Performance & Security Testing, beside load testing and security validation, and that is the right place. Unvalidated input is where most attacks on web applications begin (Chapter 31): injection, oversized bodies that exhaust memory, values that reach the database and break it. The tests of Chapter 52 ask whether the rules are right; this check asks whether anything at all can get past them.

The difference in practice is the attitude. A black-box test asks "does 6 get refused?" An input validation check asks "what happens if I send an array where a number goes, a string of 200 characters, a number larger than the column, a null, nothing at all, or ' OR 1=1 --?"

What to send

A checklist that covers most of what finds faults, for every field of every request:

KindExamples
The wrong type"3" for a number, 3 for a string, an array, an object, true
Missing and emptyabsent, null, "", [], {}
Boundariesone below, the edge, one above, for every limit
Enormous200 characters where 60 are allowed, 1e308, a number above the column's range
Strange charactersquotes, angle brackets, a null character, characters from another script
Attack shapes' OR 1=1 --, <script>alert(1)</script>, ../../etc/passwd
The request itselfnot JSON, broken JSON, a body over the limit, a missing content type
The addressan id that is a word, zero, negative, enormous, or has a leading zero

And every answer is judged against three rules:

  1. Nothing may answer 500. A server error means the application met something it did not expect.
  2. Every refusal names the field, so a page can show it where it belongs.
  3. Nothing is quietly converted. "3" must be refused, not read as 3, because a page that sends it has a bug worth finding (Chapter 47).

The sweep

// Input validation checks: every field of every endpoint that
// takes input, with values from the checklist. Nothing should
// answer 500, and every refusal should name its field.
const BASE = 'http://127.0.0.1:3000';
const jar = {};

async function send(who, method, path, body, raw) {
  const headers = {};
  if (jar[who]) headers.Cookie = jar[who];
  let payload;
  if (raw) {
    headers['Content-Type'] = raw.type;
    payload = raw.body;
  } else if (method !== 'GET') {
    headers['Content-Type'] = 'application/json';
    payload = JSON.stringify(body ?? {});
  }
  const res = await fetch(BASE + path, { method, headers, body: payload });
  const set = res.headers.get('set-cookie');
  if (set) jar[who] = set.split(';')[0];
  const data = await res.json().catch(() => null);
  return {
    status: res.status,
    code: data?.error?.code ?? '',
    fields: Object.keys(data?.error?.details ?? {}).join(','),
  };
}

const WRONG = [null, true, 0, -1, 1.5, '', ' ', 'abc', [], {},
  'x'.repeat(200), "' OR 1=1 --", '<script>alert(1)</script>',
  '../../etc/passwd', '\u0000', 1e308, 4294967296];

const results = [];
// `value` is what was sent, shortened, so that an accepted
// probe can be judged without running the script again.
const short = (v) => {
  const s = JSON.stringify(v) ?? String(v);
  return s.length <= 20 ? s : `${s.slice(0, 17)}..."`;
};
async function check(area, value, ...args) {
  const r = await send(...args);
  results.push({ area, value: short(value), ...r });
  return r;
}

(async () => {
  // Every field of registering, which is open to anyone.
  for (const wrong of WRONG) {
    await check('register name', wrong, 'anon', 'POST', '/api/auth/register',
      { name: wrong, email: 'a@college.example', password: 'a-good-one-42' });
    await check('register email', wrong, 'anon', 'POST', '/api/auth/register',
      { name: 'Test Student', email: wrong, password: 'a-good-one-42' });
    await check('register password', wrong, 'anon', 'POST', '/api/auth/register',
      { name: 'Test Student', email: 'b@college.example', password: wrong });
    await check('login email', wrong, 'anon', 'POST', '/api/auth/login',
      { email: wrong, password: 'x' });
  }
  await send('priya', 'POST', '/api/auth/login',
    { email: 'priya@college.example', password: 'canteen-demo' });
  await send('lata', 'POST', '/api/auth/login',
    { email: 'owner@college.example', password: 'canteen-demo' });
  // Every field of an order, and of the owner's screens.
  for (const wrong of WRONG) {
    await check('order slot', wrong, 'priya', 'POST', '/api/orders',
      { slot: wrong, items: [{ menuItemId: 1, quantity: 1 }] });
    await check('order items', wrong, 'priya', 'POST', '/api/orders',
      { slot: '12:40', items: wrong });
    await check('order itemId', wrong, 'priya', 'POST', '/api/orders',
      { slot: '12:40', items: [{ menuItemId: wrong, quantity: 1 }] });
    await check('order quantity', wrong, 'priya', 'POST', '/api/orders',
      { slot: '12:40', items: [{ menuItemId: 1, quantity: wrong }] });
    await check('menu name', wrong, 'lata', 'POST', '/api/menu',
      { name: wrong, category: 'snacks', pricePaise: 3000, isVeg: true });
    await check('menu price', wrong, 'lata', 'POST', '/api/menu',
      { name: 'Test Item', category: 'snacks', pricePaise: wrong, isVeg: true });
    await check('stock', wrong, 'lata', 'PUT', '/api/menu/1/stock',
      { stockLeft: wrong });
    await check('status', wrong, 'lata', 'PATCH', '/api/orders/1/status',
      { status: wrong });
  }
  // Ids in the address, and queries.
  for (const id of ['abc', '0', '-1', '1.5', '017', '99999999999',
    '1e3', '%20', '..']) {
    await check('order id', id, 'priya', 'GET', `/api/orders/${id}`);
    await check('menu id', id, 'lata', 'PUT', `/api/menu/${id}/stock`,
      { stockLeft: 5 });
  }
  for (const q of ['slot=bad', 'slot=', 'slot=12:40&slot=12:50',
    'slot[]=12:40', 'date=2026-02-30', 'date=abc', 'date[]=1']) {
    await check('query orders', q, 'lata', 'GET', `/api/orders?${q}`);
    await check('query report', q, 'lata', 'GET', `/api/reports/daily?${q}`);
  }
  // The request itself.
  await check('body', 'not JSON', 'priya', 'POST', '/api/orders', null,
    { type: 'text/plain', body: 'slot=12:40' });
  await check('body', 'broken JSON', 'priya', 'POST', '/api/orders', null,
    { type: 'application/json', body: '{"slot":' });
  await check('body', '11 kB', 'priya', 'POST', '/api/orders', null,
    { type: 'application/json', body: JSON.stringify({ p: 'x'.repeat(11000) }) });

  // The report: how each area answered, and the three rules.
  const areas = [...new Set(results.map((r) => r.area))];
  for (const area of areas) {
    const mine = results.filter((r) => r.area === area);
    const answers = [...new Set(mine.map((r) => `${r.status} ${r.code}`))];
    console.log(`${area.padEnd(17)} ${String(mine.length).padStart(3)} `
      + `probes  ${answers.sort().join(' | ')}`);
  }
  const server = results.filter((r) => r.status >= 500);
  const accepted = results.filter((r) => r.status < 400);
  const unnamed = results.filter(
    (r) => r.code === 'invalid_input' && r.fields === '');
  console.log(`\nprobes ${results.length}: server errors ${server.length}, `
    + `accepted ${accepted.length}, refusals naming no field ${unnamed.length}`);
  for (const a of accepted) {
    console.log(`  accepted: ${a.area.padEnd(17)} ${a.value}`);
  }
})();
munotes.in426

Input Validation Checks

$ cd ~/canteen-preorder
$ npm run db:setup > /dev/null
$ npm start > checks.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ node ~/checks.js
register name      17 probes  201  | 400 invalid_input
register email     17 probes  400 invalid_input
register password  17 probes  201  | 400 invalid_input | 409 email_taken
login email        17 probes  400 invalid_input | 401 wrong_credentials
order slot         17 probes  400 invalid_input
order items        17 probes  400 invalid_input
order itemId       17 probes  400 invalid_input
order quantity     17 probes  400 invalid_input
menu name          17 probes  201  | 400 invalid_input
menu price         17 probes  400 invalid_input
stock              17 probes  200  | 400 invalid_input
status             17 probes  400 invalid_input
order id            9 probes  404 not_found
menu id             9 probes  404 not_found
query orders        7 probes  200  | 400 invalid_input
query report        7 probes  200  | 400 invalid_input
body                3 probes  400 bad_json | 413 too_large | 415 json_only

probes 239: server errors 0, accepted 14, refusals naming no field 0
  accepted: register name     "abc"
  accepted: register password "' OR 1=1 --"
  accepted: stock             0
  accepted: menu name         "abc"
  accepted: menu name         "' OR 1=1 --"
  accepted: menu name         "<script>alert(1)..."
  accepted: menu name         "../../etc/passwd"
  accepted: query report      "slot=bad"
  accepted: query report      "slot="
  accepted: query report      "slot=12:40&slot=..."
  accepted: query orders      "slot[]=12:40"
  accepted: query report      "slot[]=12:40"
  accepted: query orders      "date[]=1"
  accepted: query report      "date[]=1"
$ rm ~/checks.js
munotes.in427

Input Validation Checks

Reading the report

No server error anywhere, which is the first rule and the one that matters most: 239 probes, and nothing in the checklist reached code that did not expect it.

munotes.in428

Input Validation Checks

Every refusal named its field, which is the second rule: a page can put each message where it belongs.

Every area answers in one or two ways, and each is the right one: 400 invalid_input for a value that is wrong whatever the state of the canteen, 404 for an id that cannot name a row (Chapter 47), 409 where the request is proper but the canteen's state refuses it, and 415, 400 or 413 for the request itself. Notice login email: a wrong-typed email is a 400, but a well-formed one that nobody has is 401, the same answer as a wrong password, which is control S16 (Chapter 46).

The fourteen accepted probes are the part to read, and each is correct:

AcceptedWhy it is right
register name and menu name: "abc"three letters is a name; the rule is 2 to 80, and 2 to 60 for an item
register password: "' OR 1=1 --"eleven characters, not on the common list: a fine password. No composition rules (Chapter 46)
menu name: "' OR 1=1 --", "<script>alert(1)</script>", "../../etc/passwd"they are stored as text and shown as text: the SQL is a placeholder (Chapter 44) and the page adds text, never HTML (Chapter 41)
stock: 0nothing left today is a real answer, and the rule is 0 to 1000
query report: slot=bad, slot=, a repeated slotthe daily report has no slot parameter at all, so an unknown one is ignored, as every unknown parameter is
query orders and query report: slot[]=12:40, date[]=1the names are slot[] and date[], which the application has no parameter called, so no filter is applied

The third of those rows is worth pausing on. A menu item may be named <script>alert(1)</script>, and that is the correct behaviour: the name is data, and the two places it could become code are both closed. Refusing angle brackets instead would be the blacklist Chapter 47 argues against, and it would refuse real names in other languages long before it stopped an attacker.

What the check made the team look at

One thing, and it became a decision rather than a defect. A menu item's name keeps the spacing inside it: Veg Thali is stored as typed, because the validator trims the ends and checks the length but does not collapse runs of spaces. Nothing breaks, HTML shows it as one space, and the database's unique index treats Veg Thali and Veg Thali as different names.

munotes.in429

Input Validation Checks

The worked team looked at it and left it, recording why: the owner types the names herself and sees them on her own screen at once, and collapsing spaces silently would be one more quiet change to what somebody typed. Writing that down is the point. An accepted input nobody has thought about is a hole; an accepted input with a recorded reason is a decision.

What the check does not cover

Being honest about the edges of a check is part of it:

  • It sends one value at a time. Combinations, such as a valid slot with an invalid item, are the decision table's job (Chapter 52).
  • It does not test order: two requests that race are Chapter 45's.
  • It cannot see what the page does with a refusal; that is Chapter 41, and it is where the defect of Chapter 56 lived.
  • It tests the API, not the database's own constraints, which are the last net (Chapter 29).

Do this for your project

  1. List every endpoint that takes input, and every field of each.
  2. Send the checklist's kinds to each: wrong type, missing, empty, boundaries, enormous, strange characters, attack shapes.
  3. Probe the ids in addresses and the values in queries as well as the bodies.
  4. Test the request itself: not JSON, broken JSON, too large.
  5. Judge by three rules: nothing 500, every refusal names its field, nothing quietly converted.
  6. Read every accepted probe, and either fix it or write down why it is right.
  7. Keep the script in the repository and put its report in the test summary.

Mistakes that cost marks

Checking only the happy path, which is the one nobody gets wrong.

A 500 dismissed as "it only happens with silly input", which is precisely the input an attacker sends.

Silent conversion, so "3" becomes 3 and the page's bug is never found.

Refusals with no field, which a page cannot show where it belongs.

Accepted probes unread, so the one that should have been refused is never noticed.

A check run once by hand and never again after the code changed.

Quick revision

  • The checklist: wrong type, missing and empty, boundaries, enormous, strange characters, attack shapes, the request itself, the address.
  • Three rules: nothing 500, every refusal names its field, nothing quietly converted.
  • Read the accepted probes: fix, or record the reason.
  • 400 for always wrong, 404 for an id that names nothing, 409 for wrong now, 413 and 415 for the request itself.
  • It does not cover combinations, races, what the page shows, or the database's own constraints.
munotes.in430

Input Validation Checks

Questions you must be able to answer

1. Why is input validation checking listed under security testing? Because unvalidated input is where most attacks on web applications begin: injection, oversized requests, and values that reach the database in a form nobody expected. The check asks whether anything can get past the validation at all, which is a security question.

2. How does this check differ from black-box testing of the same rules? Black-box tests ask whether the specified rules are applied correctly, with values chosen from partitions and boundaries. This check sends values outside the expected domain entirely, including wrong types and attack shapes, at every field of every endpoint, and judges the answers by rules that hold everywhere.

3. What are the three rules every answer is judged by? Nothing may answer with a server error, because that means the application met something it did not expect; every refusal of input must name the field at fault, so the page can show it there; and nothing may be quietly converted, because a value of the wrong type means the caller has a bug.

4. Why is the list of accepted probes the most interesting part of the report? Because a refusal is the expected outcome, while every acceptance is a claim that the value is valid. Reading them finds the one that should have been refused, and turns the rest into recorded decisions.

5. What did this check find in the worked project? That a menu item's name keeps the spacing inside it, so two names differing only in spaces are different names. The team left it, recording why: the owner types and sees the names herself, and silently changing what somebody typed is worse than storing it.

6. What does the check not cover? Combinations of conditions, which a decision table covers; requests that race each other, which concurrency tests cover; what the pages do with a refusal, which is seen only in a browser; and the database's own constraints.

Contents This chapter on its own page

munotes.in431

Chapter Sixty-Five

Security Validation

Syllabus topic Module 2, "Performance & Security Testing: Security validation".

In one line

Security validation is going down the security design line by line and showing, for each control, that the system really does what the design claims, by attacking it in the way that control exists to stop.

In the wording to use when asked: security validation verifies that the controls identified in the security design are implemented and effective, by exercising each against the threat it addresses, and includes reviewing dependencies for known vulnerabilities; its result is a report stating, for each control, the evidence and any residual risk accepted.

The rule for this chapter

A control is not tested by reading the code that implements it. It is tested by doing the thing it exists to prevent and finding that you cannot. So each row below is an attack, and the evidence is what came back.

Seventeen controls were designed (Chapter 31). Fourteen are shown here; the other three are shown by their own chapters and named at the end.

The automated ones

Five controls are in the test suite, named after themselves, so that the report can be produced by running it (Chapter 53):

$ cd ~/canteen-preorder
$ node --test --test-reporter=./test/reporter.js \
>   test/integration/security.test.js 2>&1 | tail -8
test/integration/security.test.js
  pass  S6 keeps only a hash of the session token
  pass  S8 treats SQL typed into a field as text
  pass  S10 refuses a request body over 10 kilobytes
  pass  S11 answers a broken body without the details
  pass  S12 marks the cookie Secure and sends HSTS under HTTPS

5 tests: 5 passed, 0 failed
  • S6, the session: the cookie's value is 32 random bytes, and what the database holds is its SHA-256, so a stolen sessions table signs nobody in.
  • S8, injection: a menu item named Tea'); DROP TABLE orders; -- is stored as text, and the orders table is still there.
  • S10, oversized input: a body over 10 kilobytes is refused 413 before any route sees it.
  • S11, error details: a broken body answers bad_json with no SyntaxError, no stack and no file name.
  • S12, the cookie under HTTPS: with COOKIE_SECURE=true the cookie is marked Secure and the site sends its HSTS header.

The ones that need an attack

S1: one student cannot touch another's order

$ cd ~/canteen-preorder
$ npm start > sec.log 2>&1 &
$ curl -s -o /dev/null --retry 10 --retry-connrefused localhost:3000/api/health
$ curl -s -c priya -o /dev/null -H 'Content-Type: application/json' \
>   -d '{"email":"priya@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ ID=$(curl -s -b priya -H 'Content-Type: application/json' \
>   -d '{"slot":"12:40","items":[{"menuItemId":1,"quantity":1}]}' \
>   localhost:3000/api/orders | jq -r .order.id); echo "Priya's order is $ID"
Priya's order is 1
$ curl -s -c kabir -o /dev/null -H 'Content-Type: application/json' \
>   -d '{"email":"kabir@college.example","password":"canteen-demo"}' \
>   localhost:3000/api/auth/login
$ curl -s -b kabir -o /dev/null -w "Kabir reads it:    %{http_code}\n" \
>   localhost:3000/api/orders/$ID
Kabir reads it:    404
$ curl -s -b kabir -o /dev/null -w "Kabir cancels it:  %{http_code}\n" \
>   -X POST -H 'Content-Type: application/json' -d '{}' \
>   localhost:3000/api/orders/$ID/cancel
Kabir cancels it:  404
$ curl -s -b kabir localhost:3000/api/orders/mine | jq -c '.orders | length'
0
munotes.in432

Security Validation

404 both times, which is the design: not "forbidden", which would confirm that the order exists, but "not found" (Chapter 43). And Kabir's own list is empty, so nothing leaked into it.

S2: a student cannot use the counter's or the owner's functions

$ cd ~/canteen-preorder
$ for p in /api/orders /api/kitchen?slot=12:40 /api/reports/daily; do \
>   curl -s -b priya -o /dev/null -w "%{http_code} $p\n" "localhost:3000$p"; done
403 /api/orders
403 /api/kitchen?slot=12:40
403 /api/reports/daily
$ curl -s -b priya -o /dev/null -w "%{http_code} POST /api/menu\n" \
>   -H 'Content-Type: application/json' \
>   -d '{"name":"Free Lunch","category":"meals","pricePaise":100,"isVeg":true}' \
>   localhost:3000/api/menu
403 POST /api/menu
$ curl -s -b priya -o /dev/null -w "%{http_code} POST /api/users/staff\n" \
>   -H 'Content-Type: application/json' \
>   -d '{"name":"Me Again","email":"me@college.example","password":"a-good-one-42"}' \
>   localhost:3000/api/users/staff
403 POST /api/users/staff

Five addresses, five 403s: deny by default, enforced by the guard in front of each route (Chapter 42).

S4 and S16: guessing, and finding out who has an account

The limiter is tested in the suite and again in Chapter 60, where its dependence on TRUST_PROXY is shown. S16, that a wrong password and an unknown email are answered alike, in time as well as in words, is measured in Chapter 46: 156 ms against 151 ms, because the service hashes a dummy password when the email is unknown.

S7: another site cannot act for a signed-in student

$ cd ~/canteen-preorder
$ curl -s -b priya -o /dev/null -w "as a form post:    %{http_code}\n" \
>   -H 'Content-Type: application/x-www-form-urlencoded' \
>   -d 'slot=12:40' localhost:3000/api/orders
as a form post:    415
$ curl -s -b priya -o /dev/null -w "from another site: %{http_code}\n" \
>   -H 'Content-Type: application/json' -H 'Origin: https://evil.example' \
>   -d '{"slot":"12:40","items":[]}' localhost:3000/api/orders
from another site: 403

An ordinary form on another website can send neither: it cannot set the content type to JSON, and its origin is not this site. Together with the cookie's SameSite=Lax that is three defences against cross-site request forgery, and the first two are shown here.

S9 and S17: the browser's own protections

$ cd ~/canteen-preorder
$ curl -s -D - -o /dev/null localhost:3000/api/menu | \
>   grep -iE 'content-security-policy|x-frame-options|x-content-type|referrer|x-powered-by'
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
X-Frame-Options: DENY

The content security policy allows scripts only from the site itself, so a script injected into a page would not run even if one ever were; the framing headers stop the site being shown inside another site's page; and x-powered-by is absent, because the application removes it (Chapter 42).

munotes.in433

Security Validation

S3: passwords

Shown in Chapter 46, run: the stored form is scrypt$16384$8$5$salt$key, two hashes of one password differ, and one hash costs about 160 milliseconds on purpose. Here is the part that matters to an attacker who has the database:

$ cd ~/canteen-preorder
$ sudo mysql canteen -e \
>   "SELECT email, LEFT(password_hash, 22) AS beginning FROM users LIMIT 3;"
email	beginning
owner@college.example	scrypt$16384$8$5$ksghO
counter@college.example	scrypt$16384$8$5$SSxy/
priya@college.example	scrypt$16384$8$5$KS08D

Nothing in that column can be turned back into a password, and the settings travel with each hash so they can be raised later.

S15: the dependencies

$ cd ~/canteen-preorder
$ npm audit 2>&1 | tail -3
found 0 vulnerabilities
$ node -e "
> const p = require('./package.json');
> console.log('direct dependencies:', JSON.stringify(p.dependencies));
> const n = require('node:fs').readdirSync('node_modules')
>   .filter((d) => !d.startsWith('.')).length;
> console.log('packages installed in all:', n);
> "
direct dependencies: {"express":"5.2.1","mysql2":"3.24.5"}
packages installed in all: 75

Two direct dependencies, at exact versions, installed from a lock file (Chapter 39), and no known vulnerability in any of the seventy-five packages in node_modules: the two, and everything they bring with them. Run this before every release, because the answer changes without your code changing: a vulnerability published tomorrow is in your project tomorrow.

S13: secrets

$ cd ~/canteen-preorder
$ grep -rn "DB_PASSWORD" .env.example src/config.js | head -3
.env.example:14:DB_PASSWORD=change-this-password
src/config.js:18:      password: env.DB_PASSWORD || '',
src/config.js:30:    throw new Error('DB_PASSWORD is not set. '
$ grep -c "^.env$" .gitignore
1
$ ls -l .env | awk '{print $1, $NF}'
-rw-r--r-- .env
$ chmod 600 .env
$ ls -l .env | awk '{print $1, $NF}'
-rw------- .env

The password is read from the environment, the example file carries a placeholder, and .env is ignored by Git (Chapter 61 shows Git really ignoring it). The last three lines are the part students miss: the file was readable by everyone on the machine, and chmod 600 makes it readable only by its owner. On the lab desktop that owner is the service's own user (Chapter 60); here it is the student.

The two this lab cannot show

  • S12's HTTPS in use needs a certificate and a public name, which the trial does not have (Chapter 25). What is shown here is the application's half: with COOKIE_SECURE=true the cookie is marked Secure and the HSTS header is sent.
  • S14, the database and the application reachable only from the machine, is a property of the deployment rather than of the code: Chapter 57 shows the application refusing the network address until it is told otherwise, and Chapter 60 configures Nginx as the only program facing it.
munotes.in434

Security Validation

The security test report

One page for the final report (Chapter 73), and the honest parts are the last two rows:

Security validation, Canteen Pre-order, 14 October 2026

Method. Each control of the security design was exercised against the threat it addresses, on the deployed system, plus the automated security tests and a dependency audit.

Result. S1 to S11 and S13 to S17: no control failed. S12 is met in the application, and not in the trial's deployment, which has no HTTPS.

Dependencies. npm audit: no known vulnerabilities in the two direct dependencies or the packages they bring.

Accepted risks. Plain HTTP on the college Wi-Fi during the trial (the largest, with its fix in Chapter 58); an 8-character minimum password against NIST's 15; no alerting on bursts of failed sign-ins; one database account with every privilege on its own two databases; and registering with an address that already has an account reveals that it is registered.

Not tested. Anything below the application: the operating system's own patches, the college network, and MySQL itself.

A security report with no accepted risks and nothing untested is not a thorough report; it is an incomplete one.

Do this for your project

  1. Write the security design first, with numbered controls (Chapter 31), and test against that list.
  2. Test each control by doing the thing it prevents, not by reading its code.
  3. Put the controls a program can check into the test suite, named after themselves.
  4. Run a dependency audit before every release, and again before the examination.
  5. Say plainly which controls your environment cannot demonstrate, and why.
  6. List the risks you accept, each with its reason.
  7. Say what you did not test at all.

Mistakes that cost marks

"The system is secure", with nothing tested.

Testing a control by reading its code, which proves only that the code exists.

A password hash pasted into a report as evidence, which is a leak, not evidence.

A dependency audit run once, in August.

No accepted risks, which means either a perfect system or an unread design.

Attacking somebody else's system to show a technique: everything in this chapter is done to the team's own application, on their own machine, and nothing else is acceptable.

Quick revision

  • Test a control by doing what it prevents; reading the code is not a test.
  • In the suite: S6 hashed sessions, S8 injection, S10 size, S11 error details, S12 the Secure cookie.
  • By attack: S1 another's order is 404, S2 five addresses 403, S7 form post and foreign origin refused, S9 and S17 the headers, S3 the stored hash, S13 the secret's place.
  • npm audit before every release: the answer changes without your code changing.
  • Say what the environment cannot show, the risks accepted, and what was not tested.
munotes.in435

Security Validation

Questions you must be able to answer

1. What does security validation add to the tests already written? It goes down the security design's list of controls and exercises each against the threat it exists to stop, on the deployed system, rather than testing that the application's features work. Its output is a statement about each control, with evidence.

2. Why is reading the code not a test of a control? Because the code may be right and unreachable, applied to the wrong route, or undone by something else. The test is to attempt the thing the control prevents and find that it fails.

3. Why does a student asking for another student's order get 404 rather than 403? Because 403 would confirm that the order exists and belongs to somebody, which is information the attacker did not have. "Not found" is the same answer an id that names nothing receives.

4. What are the three defences against another site acting for a signed-in student? The request must be JSON, which an ordinary cross-site form cannot send; if the browser states the origin, it must be this site; and the session cookie is SameSite=Lax, so it is not sent with another site's form posts.

5. Why must a dependency audit be run again before the examination? Because it reports vulnerabilities published since the last run: the project's own code need not change for the answer to change.

6. What makes a security report credible? That it says which controls were tested and how, which the environment could not demonstrate, which risks were accepted and why, and what was not tested at all. A report with none of those reads as a claim rather than a result.

Contents This chapter on its own page

munotes.in436

Chapter Sixty-Six

The Technical Report

Syllabus topic Module 2, "Final Documentation: Technical Report".

In one line

The technical report is the permanent written record of the project: the problem, what was built, how it was designed, what was verified and how, what it cannot do, and what was learned, assembled almost entirely out of documents that already exist.

In the wording to use when asked: a technical report documents a completed engineering project for a reader who was not part of it, covering the problem and its context, the requirements, the design and its rationale, the implementation, the verification performed and its results, the limitations and residual risks, and the conclusions, with references and appendices that let each claim be checked.

It is mostly already written

Students dread the report because they picture writing forty pages in the last week. Nobody has to. By this point the project has produced:

Already writtenChapterBecomes
The problem, its evidence, the objectives and the scope3 to 5the introduction
The feasibility study6 to 8a section of the introduction, and an appendix
The plan: WBS, timeline, resources16 to 18the planning section, and the comparison with what happened
The SRS34the requirements chapter
The UML set35the design chapter's figures
The architecture document36the design chapter's text
Setting up, and the six-step check39the implementation chapter, and the README of Chapter 69
The test plan, cases and report50, 55the testing chapter
The defect and its fix56the testing chapter's evidence that defects were tracked
Deployment and the APK57 to 60the implementation chapter
Load, input and security checks63 to 65the testing chapter

The report is assembly, not composition. What is genuinely new is the introduction, the results, the limitations and the conclusion, perhaps six pages of them, plus the work of making one document out of many: one voice, one set of names, one numbering.

A structure that works

MU prints no format, so use your college's if it gives one. Where it does not, this covers what a report has to do:

SectionWhat goes in it
1Introductionthe problem, the evidence, whom it affects, the objectives O-1 to O-4, the scope and the out-of-scope list
2Feasibility and planningtechnical, economic and operational feasibility; the SDLC model and why; the plan, and how the work went against it
3Requirementsthe user classes, FR-1 to FR-17, NFR-1 to NFR-12, the constraints and the assumptions
4System designthe architecture and its decisions, the six UML diagrams, the database schema, the API, the security design
5Implementationthe stack, the layers, the modules, the deployment, the Android app, and what was hard
6Testing and verificationthe plan, the levels, the results, the traceability matrix, the load report, the input and security checks
7Resultseach objective answered as far as it can be, and what remains to be measured
8Limitationswhat the system does not do, and every risk accepted
9Conclusion and future workwhat was achieved and learned, and what a next version should do first
Referencesevery standard, source and tool, with its version and the date it was read
Appendicesthe feasibility report, the full test cases, the schema, the screenshots (Chapter 68), the user manual (Chapter 67)
munotes.in437

The Technical Report

Chapter 73 is about handing this in: the binding, the certificate, the checks and the deadline. This chapter is about writing it.

Numbers, not adjectives

One habit separates a report that earns marks from one that fills pages. Every claim carries its number and the place it can be checked.

Instead ofWrite
"The system is fast.""During a simulated lunch rush, all 200 menu and order requests were answered within 1 second, median 3 ms and 95th percentile 6 ms, none failed; the machine, the data and the script are in section 6.5."
"The site is accessible.""All 19 text colour pairs in the stylesheet pass WCAG 2.2's 4.5 to 1; the weakest is the field error at 6.47 to 1. At 320 CSS pixels wide no page scrolls sideways. Section 6.6."
"Passwords are secure.""Stored as scrypt hashes at N = 2^14, r = 8, p = 5, one of the five settings the OWASP cheat sheet lists, with a random salt each and constant-time comparison. Section 4.6, and the checks in section 6.7."
"The application is reliable.""systemd restarts it within 3 seconds of a crash, which was proved by killing the service and reading the journal; NFR-10 asks for 10. Section 5.4."

Notice what each of the right-hand versions gives the examiner: a figure, the conditions it was taken under, and a section to turn to. An unprovable sentence is worth less than no sentence, because it invites the one question you cannot answer.

Results: what you may claim

This is the section students get wrong, in both directions. Some claim the project was a triumph without measuring anything; others leave the section out because the system has not been in real use long enough to prove its objectives.

The correct answer is to answer each objective as far as the evidence goes, and say plainly what is not yet measured. The worked team wrote its report between 16 and 26 October, after the acceptance session but with the canteen's two-week trial still to run, so its results section reads:

7. Results

O-1, a student orders before the break and collects within five minutes of a chosen pickup slot. Built and verified. A student can place an order for any of the four slots up to the cut-off (FR-7 to FR-11), and the counter's list moves it to Ready and Collected (FR-14, FR-15). In the acceptance session of 9 October twelve students in the queue placed real pre-orders, one of them on the Android app, and the counter moved all twelve through to collected. What this does not yet show is the wait a student actually experiences in a lunch rush; that is what the trial measures, against the 16 minutes measured in the observation week of Chapter 3.

O-2, halve the number of students who leave the queue without buying, from 31.2 a day. Not yet measurable. It will be counted during the two-week trial, at the counter, in exactly the way the observation week counted it, so that the before and the after are comparable. The report states the method, the baseline and who will count.

O-3, tell the kitchen by 12:20 how many of each dish to make for each slot. Met. The kitchen list (FR-16) shows the counts by slot, and in the acceptance session the head cook checked the 12:40 list against what the counter had told him by voice; they matched.

O-4, show the owner the day's sales without counting slips. Met. The daily report (FR-17) totals the day's collected orders, and in the acceptance session the owner read it and its totals matched the cash in the drawer.

Requirements. All 17 functional requirements are implemented and traced to tests in section 6.4. Of the twelve non-functional requirements, three were measured rather than inspected: NFR-1 in the load test, and NFR-7 and NFR-8 in the browser, in sections 6.5 and 6.6. 117 automated tests pass on one command, and the session ended with the owner agreeing to run the system for the rest of the term.

munotes.in438

The Technical Report

Two things make that extract worth copying. It is specific about the difference between built, verified and measured in use, and it treats "not yet measurable" as a result with a method attached rather than as a gap to hide. An examiner reading "O-2 will be counted during the trial, the same way as the baseline" learns more about the team than any paragraph of adjectives could tell them.

Limitations

Every limitation in the worked report was decided and written down when it arose. That is what makes this section an hour's work at the end instead of an impossible one.

LimitationDecided inWhy it stands
Ordering works only on the college Wi-Fi, over plain HTTP25, 31the lab desktop has no public name or address, so no certificate; the fix is a public server with a name (Chapters 58, 60), which also lifts the Wi-Fi limit
A password may be 8 characters, where NIST asks 15 of a single factor31, 36the accounts guard lunch orders, only college addresses can register, failed sign-ins are limited, and students type on phones
Nobody is alerted when sign-ins fail in bursts31every refusal is in the log; for one canteen, alerting was judged not worth its complexity
One database account, with every privilege on its own two databases29, 31the setup script must be able to rebuild them; a second account allowed only to read and write rows would be stricter
Registering with an address that already has an account answers "email taken"31it reveals that the address is registered, and it is the answer a real student needs; only college addresses can register
The stock is one number per item, not reset overnight14 (A-2)held by the owner's morning routine and the counter's check at 11:00; the fix, a date stored with each stock figure, is recorded for a later release
An account can be switched off only in the database24is_active exists and sign-in honours it, but the first release has no screen for it
The Android app needs the server; it does nothing offline59it is a window onto the same pages, which is what let one screen of Kotlin serve the whole system and kept the rules on the server
Nothing watches the server from outside60systemd restarts it within 3 seconds and /api/health answers anyone who asks, but no one is told if the machine itself is off
The kitchen list is on a screen, not on paper54asked for by the head cook at acceptance, out of scope for this release, and first in the future work
The counter cannot see the day's stock numbers67the owner's pages are closed to a counter account, and the students' menu names a count only below ten; the 11:00 check that guards A-2 therefore reads the sold-out notes rather than numbers
munotes.in439

The Technical Report

Do not hide these. An examiner who finds a limitation the report does not mention concludes the team did not know about it; one who reads it in the report, with its reason and its fix, concludes the opposite. The limitations section is where a report stops being a brochure.

A limitation is not the same as a defect. Defects get fixed or tracked (Chapter 56). Limitations are decisions, and each one belongs in the report with the reason it was accepted.

Conclusion and future work

Future work is not a wish list. It is the requirements that were prioritised and not built, plus the limitations with known fixes, both of which the project has been recording all along (Chapter 13):

munotes.in440

The Technical Report

9.2 Future work. Three requirements were prioritised as Could Haves and not built: a browser notification when an order is ready (6 hours), earlier days' orders on the My orders page (4 hours), and a student's usual items shown first (4 hours). The Coulds were 14 of the plan's 78 hours, and the time they allowed for went instead into making the stock rule safe when several students order at once, which took longer than its six-hour estimate; that is what the contingency was for. Ahead of those three we would put five changes that come out of this report's limitations: a printed kitchen list, which the head cook asked for at acceptance; a public server with a name and HTTPS, which also removes the Wi-Fi limit; a date stored with each day's stock, so that yesterday's figures cannot be offered by accident; a stock view for the counter, so that the 11:00 check reads numbers rather than notes; and a screen for the owner to switch an account off.

And what was learned, which is the part an examiner reads for evidence of thought rather than effort. Three from the worked project, each of which happened in this book:

  • The design documents paid for themselves at the first disagreement. What the counter's list shows and what the kitchen's list shows had been written down, so a doubt during the build was settled by reading the SRS rather than by arguing.
  • The hardest part was not the amount of code. It was the stock rule when several students order at once: four lines of SQL, and two days of understanding what they had to guarantee (Chapter 45).
  • The faults that mattered were found by people, not by the suite. All 117 automated tests passed throughout; the sentence a student would have read, "Only 2 Chicken Biryani left. 3 2", was caught by running a written test case by hand (Chapters 55, 56). Tests check what you thought of. A person looking at the screen checks what you did not.

How long, and in what voice

MU prints no page count. A report needs enough to be checked and no more; the rule that keeps it in bounds is no section repeats another. The worked report came to thirty numbered pages with twenty-three more of appendices, and Chapter 73 prints its contents page. The design chapter says how the requirements are met, not what they are; the testing chapter names the requirement each test covers instead of restating it.

Four habits of the voice:

munotes.in441

The Technical Report

  • Past tense for what was done, present for what the system does. "We measured the contrast of every colour pair" and "the server refuses an order after the cut-off".
  • Say who decided, and why. "We chose scrypt because Node.js 22, which NFR-12 supports, has no Argon2" is a sentence an examiner can question and you can defend; "scrypt was used" is neither.
  • Number every figure and table, and caption it, exactly as the Module 1 documents did (Chapter 32). Refer to them by number in the text.
  • Name versions and dates for everything you cite: Node.js 24.21.0, MySQL 8.0.46, WCAG 2.2, the OWASP Top 10 of 2025, each with the date you read it.

Do this for your project

  1. Assemble; do not compose. Start from the documents you already have and make them one document.
  2. Answer each objective with the evidence you have, and give the method for anything not yet measured.
  3. Keep a limitations list from the first week, and put every accepted risk in it with its reason and its fix.
  4. Give every claim a number, the conditions it was taken under, and a section to check it in.
  5. Make future work the Could Haves you did not build, with their estimates, ahead of nothing invented on the day.
  6. Say what you learned, including what turned out harder than you estimated.
  7. Use one set of names and one numbering throughout, and let no section repeat another.
  8. Read it once as a stranger: every figure captioned, every reference dated, nothing claimed that the report cannot support.

Mistakes that cost marks

A report started in the last week, which becomes a rewrite of everything instead of an assembly of it.

Adjectives instead of numbers: fast, secure, robust, user-friendly.

No limitations section, which no examiner believes.

Claiming outcomes nobody measured, for example a percentage improvement from a trial that has not been run. One invented number costs more than the honest sentence would have.

Future work invented on the day, unconnected to anything the project prioritised.

The SRS pasted in twice, once as requirements and once as design.

Screenshots of a version that no longer exists (Chapter 68), and references with no version or date.

Quick revision

  • The report is assembly: introduction, feasibility and planning, requirements, design, implementation, testing, results, limitations, conclusion and future work, references, appendices.
  • What is new at the end: the introduction, the results, the limitations, the conclusion.
  • Numbers, not adjectives: a figure, the conditions, and a section to check it in.
  • Results answer each objective as far as the evidence goes, and give the method for what is not yet measured.
  • Limitations are decisions, recorded when made, each with its reason and its fix; defects are tracked and fixed, which is different.
  • Future work = the Could Haves not built, with their estimates, plus the limitations with known fixes.
  • One voice, one numbering, no section repeating another; Chapter 73 hands it in.
munotes.in442

The Technical Report

Questions you must be able to answer

1. What is the technical report, and who is it for? The permanent record of the project for a reader who was not part of it: the problem, what was built, how it was designed, what was verified, what it cannot do and what was learned. For this paper the external examiner reads it, with five marks for it, having never seen the project before.

2. Why is most of the report already written before you start writing it? Because the proposal, the feasibility report, the SRS, the UML set, the architecture document, the test plan and the test, load and security reports were all written as the work was done. What remains is the introduction, the results, the limitations and the conclusion, and the editing that turns many documents into one.

3. How do you report an objective the project has not yet had time to measure? As a result with a method: what was built and verified, what the baseline is, how it will be measured, by whom and when, and in the same way as the baseline so the two are comparable. What you never do is supply a number nobody took.

4. What is the difference between a limitation and a defect? A defect is behaviour that does not match the specification: it is tracked and fixed. A limitation is a decision about what the system will not do, taken with a reason. Defects belong in the bug tracker and the test report; limitations belong in the report, each with its reason and its fix.

5. Why does a good report list its limitations? Because they are real and an examiner will find them. Listed with its reason and its fix, a limitation shows that the team understood its own system; found by the examiner in a report that does not mention it, the same limitation shows the opposite.

6. What belongs in future work, and what does not? The requirements prioritised and not built, with their estimates, and the limitations that have known fixes. What does not belong is anything thought of on the day of submission: future work is a record the project already kept, which is what keeps it honest rather than aspirational.

7. How do you keep a forty-page report from repeating itself? By giving each section one job and letting it refer to the others by number: requirements are stated once and thereafter cited as FR-7 or NFR-1, and the design, implementation and testing chapters each say something different about them.

Contents This chapter on its own page

munotes.in443

Chapter Sixty-Seven

The User Manual

Syllabus topic Module 2, "Final Documentation: ... User Manual".

In one line

A user manual tells the people who will use the system how to do their own work with it, in their words, one step at a time, including what to do when something goes wrong.

In the wording to use when asked: a user manual is task-oriented end-user documentation; it is organised by the tasks the user performs rather than by the system's features, uses the user's vocabulary and the exact words shown on screen, states the result of each step, and covers error conditions and recovery.

It is not a small technical report

The two documents MU asks for at the end of Module 2 are written for different people, and the commonest mistake is to write the manual as a shorter version of the report.

Technical reportUser manual
Readerthe examiner, a future developerthe canteen owner, the counter staff, a student
Answershow was this built, and whyhow do I do my job with it
Organised bythe project's phasesthe reader's tasks
Vocabularythe system's: endpoint, transaction, entitythe reader's: order, slot, stock, takings
Voicepast tense, third person: "the stock rule was implemented"present tense, second person, imperative: "tap Mark ready"
Mentionsarchitecture, tests, limitationsnothing the reader cannot see on the screen

The word "API" does not appear in a user manual. Neither does "database", "endpoint", "session" or "validation". If the reader cannot see it, they cannot act on it.

Eight rules that make a manual usable

  1. Organise by task, not by screen. "Set the day's stock", not "The owner page".
  2. One action to a numbered step, and put the words the screen uses in bold exactly as they appear: Place order, not "the order button".
  3. Say what success looks like. A step the reader cannot confirm is a step they will repeat.
  4. Write the plainest sentence that is still true. Short sentences, everyday words, present tense.
  5. Cover what goes wrong, with the message the system actually shows and what to do next.
  6. Say who can do it. A manual that shows the counter a screen it cannot open teaches distrust.
  7. Keep the daily work to one page. The counter staff will read a card taped beside them, not a booklet.
  8. Test it on a real user, watching, without helping. Whatever they hesitate over is a defect in the manual.

The worked manual

Canteen Pre-order's manual has four parts and eleven pages:

PartForPages
1The counter: the day's work1 (the card, taped beside the counter)
2The owner: the morning routine, the menu, accounts, the report4
3Students: ordering and collecting1 (a poster, and the same text on the notice board)
4When something goes wrong: every message, and what to do3
Who to call, and what the system does not do2
munotes.in444

The User Manual

Part 1: the counter's card

This is the whole of page 1, and it is the page that matters most, because it is used by two people at the busiest moment of the day.

The counter, every day

Sign in at the canteen tablet with your own email and password. You stay signed in all day.

1. At 11:00, check the stock. Open the Menu page and read down today's dishes. Before the canteen has sold anything, no dish should say Sold out or Only 2 left. If any does, the owner has not set today's numbers: telephone her before the break. Nothing else in the day matters as much as this.

2. During the break, work the slot. On the Counter page choose the Pickup slot you are serving. The list updates itself every ten seconds; you never need to reload it.

For each order, in order:

| When | Tap |

| --- | --- |

| You begin making it | Start preparing |

| It is on the counter, waiting for the student | Mark ready |

| The student has taken it and paid | Collected and paid |

| The slot is over and nobody came | Not collected |

3. Use the kitchen list. Still to make on the same page counts what this slot still needs, dish by dish. Read it out to the kitchen at the start of each slot.

4. Read the order number aloud when the food is ready. It is the large number at the top of each order.

Two things you cannot do, and that is on purpose: you cannot change a price or the stock, and you cannot cancel a student's order. Only the owner sets prices and stock; only the student cancels, and only before you tap Start preparing. The owner's pages do not open for a counter account: the screen answers "Your account is not allowed to do that."

Everything in that card is a task the counter performs, in the order of the day, with the words that are on the tablet in front of them. Nothing in it explains how any of it works.

What the card cannot say, because the system cannot do it. The counter has no screen showing the stock numbers: the owner's pages are closed to a counter account, and the students' menu names a count only when a dish is down to single figures. Writing this page is what made that plain, so the 11:00 check reads the sold-out notes instead, which is enough to catch a morning the owner has forgotten, and a stock view for the counter went into the report's limitations and its future work (Chapter 66). A manual is a design review of its own kind: writing down what a user must do, step by step, finds the steps the system has not given them.

munotes.in445

The User Manual

Part 2: the owner's morning routine

Assumption A-2 of Chapter 14 is the reason this section exists, and the reason it begins where it does. The stock is one number per dish and nothing resets it overnight, so the manual's first instruction of the day is to set it:

Your morning, before 11:00

1. Open the canteen page on your phone and Sign in.

2. Tap Owner. Everything in this part of the manual is on that one page.

3. Under Menu and today's stock, put today's number in the Stock left box for every dish you are offering, and tap Save on that dish. This is how many of it students may pre-order today. Food you sell over the counter to people who have not ordered is not counted here.

4. Put 0 against anything you are not making today, and it shows to students as Sold out. For a dish you have stopped making altogether, clear its On today box instead: it then reads Not on the menu today and comes back the day you tick it again.

5. Check the Price (₹) box of anything whose price has changed, and Save.

Why this is the first thing you do. The system holds one stock number for each dish, and it does not empty overnight. If you do not set today's numbers, students are offered yesterday's, and the kitchen will be asked for food you have not made. The counter staff check at 11:00 and will telephone you. A later version will store the date with each number so that an old one can never be offered; until then, this routine is the safeguard.

Then the tasks that come up less often, each in the same shape: Add an item (name, category, price in rupees, then set its stock), Add a counter staff account (name, email, a password of 8 or more characters, which you give them and they may change), and:

The day's takings, at closing

1. Tap Owner, and read Today's report. Its first line names the day and counts the orders on it.

2. Collected today is the total of every order a student collected and paid for today.

3. The table below it lists each Item sold, its Quantity and its Takings.

4. Orders that were placed and not collected are not in these totals. Nothing is owed for them.

The report counts pre-orders only. Money taken at the counter from walk-in customers is not in it, because the system never sees those sales.

munotes.in446

The User Manual

That last paragraph is the sentence that saves a support call, and it is the kind of sentence only a manual carries: it says what the number does not include.

Part 3: students

Students get one page, because a queue does not read a manual. The team wrote it as a poster for the canteen wall and the same words on the college notice board:

Order lunch before the break

1. Go to the canteen page on the college Wi-Fi and Create an account with your college email and a password of 8 or more characters. You only do this once.

2. Sign in, and on Today's menu tap + for each dish you want, up to 5 of a dish and 10 items in all. Your order on the right adds up the Total.

3. Choose a Pickup time. A slot closes 15 minutes before it begins, so the 12:40 slot closes at 12:25.

4. Tap Place order. One order for each slot.

5. My orders shows your order and updates itself every 15 seconds: Placed, then Being prepared, then Ready to collect.

6. Collect it at the counter at your slot and pay there. Say your order number.

Changed your mind? Cancel this order on My orders, then Yes, cancel it. You can cancel until the counter starts preparing it, not after.

Six steps, no jargon, and it ends with the thing a student most needs to know: pay at the counter.

Part 4: when something goes wrong

This is the part students skip when writing and users turn to first. Each row is a message the system really shows, in the words it really uses:

The screen saysWhat it meansWhat to do
"The email or the password is wrong."one of the two does not matchtry again; the owner can make you a new password
"Too many failed sign-ins. Try again later."five wrong passwords in a rowwait fifteen minutes, or ask the owner
"An account with this email already exists."you have registered beforesign in instead
"Use a password of 8 to 128 characters."the password is too shortchoose a longer one
"This password is too common. Choose one that is hard to guess."the password is on a published list of common oneschoose one you use nowhere else
"Ordering for the 12:40 slot has closed."the slot closed 15 minutes before it beginschoose a later slot
"You already have an order for the 12:40 slot."one order for each slotcollect that one, or cancel it and order again
"Only 2 Chicken Biryani left."the day's stock is nearly goneorder fewer, or choose another dish
"Chicken Biryani is sold out."today's stock of that dish is finishedchoose another dish
"Please sign in first."you have been signed outsign in again; nothing you placed is lost
"Your account is not allowed to do that."that page belongs to another rolethe counter cannot open the owner's tools
"Some details need correcting."one of the boxes above is marked in redread the red line under the box it names
The page will not load at allyou are not on the college Wi-Fi, or the server is offconnect to the college Wi-Fi; if it still fails, tell the lab in-charge
munotes.in447

The User Manual

Two rules for a table like this. Quote the message exactly, because the reader is looking for the words in front of them, not a paraphrase. And give an action, not an explanation: "wait fifteen minutes, or ask the owner" is help; "the rate limiter has been triggered" is not.

The half-hour that goes with it

Chapter 8 promised a training session, and the manual is what makes it short. The worked team's session, at the canteen, lasted thirty minutes: the owner set that day's stock herself while they watched, each counter person worked one slot's orders through the four buttons, the card went up beside the counter, and the last five minutes were the troubleshooting page, read aloud.

Watch, do not help, the rule of Chapter 54, applies here too. Every hesitation is a line of the manual to rewrite. Two came out of that half hour: the stock box needed the sentence about walk-in customers not being counted, and the counter card needed "you stay signed in all day", because Ganesh signed in again each time the tablet slept.

Who to call, and what it does not do

The last part of the manual is two pages that no student thinks to write:

  • Who to call, with names: the team for the first term, the IT lab in-charge for the machine, and what each can help with.
  • What the system does not do: no online payment, no delivery, no SMS, no notification when an order is ready, no orders from earlier days, no penalty for a student who does not collect. The same list as the report's limitations and future work (Chapter 66), in the reader's words rather than the examiner's.

Saying what it does not do is part of the manual, not an admission. A user who knows there is no notification looks at My orders; one who assumes there is, waits.

munotes.in448

The User Manual

Where it goes

  • Printed and taped beside the counter, which is the copy that gets used.
  • An appendix of the report (Chapter 66), and a page of the submission (Chapter 73).
  • In the repository, beside the project's other documents, so that it is versioned with the code it describes and a label that changes can be followed by the sentence that quotes it.

The README of Chapter 69 is a different document for a different reader: it tells a developer how to run the project. A user manual never mentions npm.

Do this for your project

  1. List your users' tasks, in the order of their day, and make each one a section.
  2. Write each task as numbered steps, one action each, with the screen's own words in bold.
  3. Say what the reader should see after each step.
  4. Put the daily work on one page, and print it.
  5. Write the troubleshooting table from your code's actual messages, and give each an action.
  6. Say plainly what the system does not do, and who to call.
  7. Watch a real user follow it without help, and fix every hesitation.
  8. Keep it with the code, and change it when a label changes.

Mistakes that cost marks

A manual that is the report again, with architecture and test results in it.

Screens described instead of tasks: "The owner page has four sections."

Paraphrased error messages, which the reader cannot match to what they are looking at.

No troubleshooting at all, which is the section users actually read.

Jargon: endpoint, payload, session, validation, transaction.

Labels that do not match the application, usually because the manual was written from the design and never checked against the built screens.

A booklet for a counter, when what was needed was one page.

Quick revision

  • The manual is task-oriented, for the user, in the user's words and the screen's words.
  • Report: how it was built, past tense. Manual: how to do your job, imperative.
  • One action a step, say what success looks like, bold the screen's labels.
  • The worked manual: the counter's one-page card, the owner's morning routine, the students' poster, the troubleshooting table, who to call and what it does not do.
  • The owner's routine begins with setting the day's stock, and the counter checks it at 11:00: that is how assumption A-2 is guarded.
  • Quote error messages exactly and give an action for each.
  • Watch a user follow it; every hesitation is a defect in the manual.
munotes.in449

The User Manual

Questions you must be able to answer

1. How does a user manual differ from a technical report? In reader, organisation and voice. The report tells an examiner or a developer how the system was built and why, arranged by the project's phases, in the past tense. The manual tells a user how to do their own work, arranged by their tasks, in the imperative, with nothing in it that the user cannot see on the screen.

2. Why is the manual organised by task rather than by screen? Because a user arrives with a job to do, not with a screen to explore. "Set the day's stock" is what the owner wants; "the owner page" is how the system happens to be laid out, and it may change.

3. Why must the manual quote the application's exact words? Because the reader is matching what they read against what is in front of them. A paraphrased button or a reworded error message costs them the one thing the manual is for, and it is also a sign the manual was written from the design rather than the built system.

4. Why does the worked manual begin the owner's day with setting the stock? Because the system holds one stock number per dish and nothing resets it overnight (assumption A-2). If the owner does not set today's numbers, yesterday's are offered to students. The routine, plus the counter's check at 11:00, is the guard the team chose for the first release, and the limitation and its proper fix are recorded in the SRS and the report.

5. What belongs in a troubleshooting section? The system's real messages, quoted exactly, each with what it means in the user's terms and an action to take. It should also cover the failures that show no message at all, such as a page that will not load because the device is off the college Wi-Fi.

6. Why does a manual say what the system does not do? Because a user who does not know waits for something that will never happen, or telephones about it. The same list as the report's limitations, written in the reader's words, prevents both.

7. How do you know the manual is good enough? One of its users performs the task from it, watched and unhelped, and does not hesitate. Anything they hesitate over is rewritten, which is how two sentences of the worked manual came to exist.

Contents This chapter on its own page

munotes.in450

Chapter Sixty-Eight

Screenshots

Syllabus topic Module 2, "Final Documentation: ... Screenshots".

In one line

Screenshots are the report's evidence that the software exists and does what the text says, so each one is taken from a known state, captioned with what it shows, and retaken after the last change to that screen.

In the wording to use when asked: screenshots are dated visual evidence of the delivered system's behaviour; each is captured from a reproducible state, numbered and captioned to say what it demonstrates and which requirement it satisfies, and kept current with the software it depicts.

Why they are a deliverable and not decoration

The examiner reads your report before they see your demonstration, and in some colleges a different examiner reads it afterwards. Until the demonstration, the screenshots are the only evidence that the system was built at all. They answer three questions the text cannot:

  • Does it exist? A page with real data on it is harder to fake than a paragraph.
  • Is it finished? Twelve screens covering every requirement say one thing; three screens and an apology say another.
  • Is it yours? The screens match the wireframes, the SRS's names and the class diagram's words, which is what a designed system looks like.

They are also the fastest way to lose marks, for one reason: a screenshot is a claim with a date on it. If the order number in your screenshot is small and the report says it was made bigger after acceptance testing, the examiner has found a contradiction in your own evidence.

Decide the shot list from the requirements

Do not walk through the application taking pictures. Start from the requirements, and take the shot that proves each one. The worked report's appendix C is twelve screenshots, and this is the list that produced them:

Screen and stateProves
1The sign-in page, with Create an account open below itFR-1, FR-2
2Registration refusing a non-college address, the red line under the boxFR-1, and that the server checks
3Today's menu, the four categories, with Chole Bhature reading Not on the menu today as the seed leaves itFR-4, FR-6
4Your order with two lines and a Total, and the Pickup time showing 12:40 closing at 12:25FR-7, FR-8
5The refusal "Only 2 Chicken Biryani left." after asking for three, with the owner having set that dish's Stock left to 2 firstFR-11
6My orders today, one order Ready to collect with its number largeFR-12
7The cancel question, Yes, cancel it and Keep itFR-13
8Orders at the counter for the 12:40 slot, three orders at different statuses, all four buttons visible across themFR-14, FR-15
9Still to make for that slot, counted by dishFR-16
10Menu and today's stock, with the Stock left boxes filled and one On today box clearFR-5, FR-6
11Today's report: the day, Collected today, and the table of Item sold, Quantity, TakingsFR-17
12The Android app showing the same menu on a phonethe APK of Chapter 59
munotes.in451

Screenshots

Read down the right-hand column: every functional requirement is covered, and nothing is photographed twice. That is the test of a shot list. Two more shots go in the implementation chapter rather than the appendix, because they are evidence of deployment and not of a requirement: the service running under systemd, and the browser at 320 pixels wide with the page still whole.

Make the state reproducible

The reason students end up with bad screenshots is that they take them from whatever the application happens to contain at midnight. Every project in this book's shape has three switches that give you the same picture every time.

The seeded accounts and dishes. npm run db:setup loads sql/seed.sql: Lata Pawar the owner, Ganesh More at the counter, and the students Priya Menon, Kabir Singh, Ananya Rao and Yusuf Khan, all with the password canteen-demo, and fourteen dishes with prices and stock. Never take a screenshot with a real student's name or address in it, and you never have to: the demonstration data is there for this.

The clock. Screens like the counter's exist only during the lunch break, and a screenshot session at 10 p.m. shows every slot closed. DEMO_TIME in .env stops the clock at an instant you choose:

ForSet DEMO_TIME toBecause
shots 3 to 5, a student ordering2026-09-29T10:30:00+05:30every slot is open, so the Pickup time list is full
shots 6 to 9, the counter at work2026-09-29T12:35:00+05:30the 12:40 slot has closed to new orders and is being served
shot 11, the day's report2026-09-29T14:00:00+05:30the day's orders are collected and the totals are final

Leave DEMO_TIME empty for real use. A screenshot taken with it set is honest as long as the report says the data is demonstration data, which is what appendix C's first line says.

The window. Decide one width and keep it. The worked shots were taken in a browser window 1280 pixels wide for the counter and owner pages and 390 wide for the student's, which is a phone's width, because that is how each is really used. A picture of a phone page in a 1600-pixel window with two thirds of it empty teaches the examiner nothing.

A repeatable order of work, which takes about twenty minutes for twelve shots:

  1. npm run db:setup to put the data back.
  2. Set DEMO_TIME for the group of shots you are taking, and npm start.
  3. Sign in as the account that screen belongs to.
  4. Place the orders the shot needs, in the same words every time.
  5. Take the shot, name the file for what it shows, and write its caption at once.
munotes.in452

Screenshots

Capturing and captioning

Take a real screenshot, never a photograph of a screen. Cmd+Shift+4 on a Mac, Win+Shift+S on Windows, or the browser's own full-page capture. A photograph of a monitor, with the room's reflection in it, is the single clearest sign of a report assembled in a hurry.

Crop to the browser's content, or include the address bar deliberately when the address is the point. Close every other tab, hide a personal bookmarks bar, and shut the developer tools.

Caption every shot, and number it. A caption says what the reader is looking at and what it proves:

Figure C.8: the counter's list for the 12:40 slot, 29 September 2026. Order 15 is ready to collect, order 16 is being prepared, and order 17 has just been placed. The four buttons are the only moves the counter may make (FR-14, FR-15).

Three things in that caption, and all three matter: what screen, what state, and which requirement. A screenshot with no caption is a decoration; the same screenshot with that caption is evidence.

Keep them as PNG, not JPEG. Text and flat colour compress badly as JPEG and come out fringed, which on a printed report looks like a low-quality scan.

Save the files with names that say what they are, in one folder, in the repository: docs/screenshots/08-counter-1240.png. The report then cites the folder, and the next person to change the counter page knows exactly which picture to retake.

Retake them last

The screenshots are the last thing you make before the report is bound. In the worked project the counter's order number was made larger on the afternoon of 9 October, because Ganesh said at the acceptance session that he read it twice (Chapter 54). Every counter screenshot taken before that afternoon shows the old size, and the report's own text describes the change. The rule that prevents the contradiction is mechanical:

After the last change to any screen, retake every screenshot of that screen. Twelve shots from a seeded database with a fixed clock is twenty minutes' work, which is why the state was made reproducible in the first place.

The same rule applies to the presentation (Chapter 74) and to the README (Chapter 69): both carry pictures, and both go stale the same way.

Do this for your project

  1. Build the shot list from your requirements, one shot for each, none twice.
  2. Reset to seeded demonstration data before a session; never screenshot a real user's data.
  3. Fix the clock, or otherwise fix the state, so the same picture can be taken again.
  4. Choose one window width for each kind of screen and keep it.
  5. Screenshots, not photographs; PNG; no developer tools, no personal tabs.
  6. Number and caption each one: what screen, what state, which requirement.
  7. Keep them in the repository with meaningful names.
  8. Retake every affected shot after the last change to a screen, and do it before binding.
munotes.in453

Screenshots

Mistakes that cost marks

Photographs of a monitor, taken with a phone.

Screenshots of a version that no longer exists, contradicting the report's own text.

Five pictures of the same page and none of the owner's report.

No captions, so the examiner must guess what each proves.

Real students' names, addresses or orders in the pictures.

Unreadable text, because the page was captured zoomed out or the image was scaled down to fit.

Developer tools open, a personal bookmarks bar, or twenty other tabs across the top.

An error message in the corner that the report never mentions.

Quick revision

  • Screenshots are evidence, and the only evidence before the demonstration.
  • Build the shot list from the requirements: one for each, nothing twice.
  • Make the state reproducible: seeded data, a fixed clock (DEMO_TIME), one window width.
  • Never a real user's data; the demonstration accounts exist for this.
  • Real screenshots, PNG, cropped, nothing personal on screen.
  • Number and caption: what screen, what state, which requirement.
  • Retake after the last change, before binding; the counter's order number is the worked example.

Questions you must be able to answer

1. Why are screenshots a deliverable in their own right? Because the report is read before the system is seen, and until the demonstration they are the only evidence that the software exists and behaves as described. They also show whether the finished screens match the design the report presents.

2. How do you decide which screenshots to take? From the requirements, not from the application. Each functional requirement gets the one shot that proves it, which is also how you notice that a requirement has no screen to photograph.

3. What makes a screenshot reproducible, and why does that matter? Seeded demonstration data, a fixed clock and a fixed window width. It matters because screens go stale: when a page changes, the shot must be retaken, and that is twenty minutes' work only if the state can be recreated exactly.

4. What belongs in a caption? The screen, the state it is in, and what it proves, with a requirement number. "The counter's list for the 12:40 slot: order 15 ready, order 16 being prepared, the four moves the counter may make (FR-14, FR-15)."

munotes.in454

Screenshots

5. Why must screenshots never contain real users' data? Because a report is read, copied and kept by people who were never told about it, and a student's name beside their lunch order is their business. The seeded demonstration accounts exist precisely so that no real data is ever needed.

6. When should screenshots be taken? Last, after the final change to the screens, and before the report is bound. A screenshot older than the software it depicts contradicts the report that contains it, and that contradiction is the examiner's next question.

Contents This chapter on its own page

munotes.in455

Chapter Sixty-Nine

Source Code Documentation

Syllabus topic Module 2, "Final Documentation: ... Source Code Documentation".

In one line

Source code documentation is what a developer who has never met you needs in order to run the project, find their way around it, and change one thing safely: a README that works, comments that say why, and names that make most comments unnecessary.

In the wording to use when asked: source code documentation comprises the in-source documentation (identifier names, file headers and comments recording rationale and constraints) and the accompanying documents (the README, folder-level notes, the schema and API references) that together let a developer build, run, navigate and modify the system without its authors.

Two halves, and the marks are in both

In the codeAbout the code
Whatnames, file headers, commentsREADME, folder notes, schema, API list
Answerswhy is this line like thishow do I run and find things
Read bywhoever changes the filewhoever arrives at the project
Graded through"technical design", and the vivathe examiner's first five minutes

The examiner's first five minutes with your repository are spent on the README. If it does not work, everything after it is read with suspicion; if it works, the rest is read with trust. That is the whole argument for writing it properly.

The README

NFR-11 says a new developer can run the system within thirty minutes, and this is the file that has to keep that promise. It opens with the same six steps that proved every machine in Chapter 39: install the libraries from the lock file, make the settings file, make the database and its account, build the tables, start the server, and ask it whether it is well.

# Canteen Pre-order

Students order lunch from the college canteen before the break and
collect it at a pickup slot they choose; the counter works through the
orders slot by slot, the kitchen sees what to make, and the owner sets
each day's stock and reads the day's takings. Payment is in cash at the
counter.

Built for Mini Project I, TY B.Sc. Computer Science, Semester 5.

## Run it in half an hour

You need Node.js 22 or later (24 is what we run) and MySQL 8.0 or later.

    git clone <this repository> canteen-preorder
    cd canteen-preorder
    npm ci                                # 1. libraries, from the lock file
    cp .env.example .env                  # 2. the settings file
    sudo mysql < sql/create-database.sql  # 3. databases and the account
    npm run db:setup                      # 4. tables, and demonstration data
    npm start                             # 5. the server, on port 3000
    curl localhost:3000/api/health        # 6. ask it whether it is well

Step 6 answers `{"status":"ok","database":"ok"}`. Open
<http://localhost:3000> and sign in.

Two notes. The password in `.env` must match the one in
`sql/create-database.sql`; change both before a real server. On Windows,
run step 3 in the Command Prompt as
`mysql -u root -p < sql\create-database.sql`, because PowerShell does
not accept `<`.

## Signing in

`npm run db:setup` loads `sql/seed.sql`, which makes these accounts.
Every one of them has the password `canteen-demo`.

| Email | Role | Sees |
| --- | --- | --- |
| `owner@college.example` | owner | the menu, the day's stock, the report, staff accounts |
| `counter@college.example` | staff | the orders at the counter, and what the kitchen still has to make |
| `priya@college.example` | student | today's menu, and their own orders |

There are three more students: `kabir@`, `ananya@` and `yusuf@`.

**These accounts are for development and testing.** Delete them, or
change every password, before real students use the site.

## The tests

    npm test            # every test: unit, black box and integration
    npm run test:unit   # the unit tests alone, and they need no database
    npm run layers      # nothing reaches past its own layer
    npm run race        # many orders at once take the stock exactly once
    npm run load        # the lunch rush, against a running server

`npm test` and `npm run race` rebuild `canteen_test` from
`sql/schema.sql` and work only there, so the real database is never
touched. `npm run load` is the only one that needs a server already
running.

## Where things are

| Folder | What is in it |
| --- | --- |
| `src/` | the server: routes, services, rules, validation, the store |
| `public/` | the pages, the stylesheet and the browser's own code |
| `sql/` | the database: `create-database.sql`, `schema.sql`, `seed.sql` |
| `test/` | `unit/`, `blackbox/` and `integration/` |
| `scripts/` | setting up the database, and the checks above |
| `deploy/` | the systemd unit and the Nginx file, and how to install them |
| `android/` | the Android app that shows these pages on a phone |
| `docs/` | the UML sources, the design drawings and the plan |
| `data/` | the list of common passwords that registration refuses |

The server's code is in layers, and each may call only the one below
it: routes, then services and validation, then rules, then the store,
which alone speaks SQL. `npm run layers` proves it.

## The settings

`.env` is not in this repository; `.env.example` is, with a comment on
every line. The ones you are most likely to change:

| Setting | What it does |
| --- | --- |
| `PORT`, `HOST` | where the server listens |
| `DB_*` | the database, and the account the application uses |
| `EMAIL_DOMAIN` | only addresses at this domain may register as students |
| `SESSION_HOURS` | how long a sign-in lasts |
| `COOKIE_SECURE` | `true` once the site is served over HTTPS |
| `APP_TIME_ZONE` | the canteen's own clock, whatever the server's is |
| `DEMO_TIME` | stops the clock at one instant, for a demonstration |
| `TRUST_PROXY` | `loopback` when the application sits behind Nginx |

`TRUST_PROXY` matters more than it looks: behind Nginx with the default
`false`, every request appears to come from Nginx, and the limit on
failed sign-ins is then counted against everyone together.

## On a server

`deploy/README.md` says what goes where: the application in
`/opt/canteen`, owned by a user who owns nothing else; its settings in
`/opt/canteen/.env`, readable only by that user; systemd to keep it
running; Nginx in front of it.

## The Android app

`android/` is one screen of Kotlin that shows these same pages, so the
rules stay on the server and a change to the pages needs no new app.
Set `SERVER_URL` in `android/app/build.gradle.kts` to the address of
your server before building.
munotes.in456

Source Code Documentation

Seven sections, and each one answers a question the reader is about to ask. Notice what it does not contain: no architecture essay, no list of features written as marketing, no history of the project, and no promises about a next version. Those belong in the report (Chapter 66). A README earns its place by being correct, not by being long.

munotes.in457

Source Code Documentation

Four details in it are worth copying into your own:

  • The commands are in the order you must run them, numbered, with what each does beside it. A reader can follow it without reading a word of prose.
  • It says what step 6 answers. A step whose success cannot be recognised is a step that gets repeated.
  • It names the traps that cost an afternoon: the two places the database password must match, the one thing Windows does differently, and TRUST_PROXY behind Nginx, which silently turns the limit on failed sign-ins into a way of locking everybody out at once (Chapter 60).
  • It warns about the demonstration accounts. A seeded password in a public repository is a real risk, and the file that creates them says so too.

Comments: why, not what

The rule is one line long. A comment that says what the code says is noise; a comment that says why the code is like this is documentation, because the why is the only thing the code cannot tell the next reader.

Two comments from this project. The first is the head of the rules module, and it exists to stop the next developer from adding a database call:

// The canteen's rules as pure functions: no database, no
// network and no clock of their own. Each one is given what
// it needs, which is what makes every one of them testable.
// ...
// Which status may follow which, and whose move it is.
const MOVES = {
munotes.in458

Source Code Documentation

The second sits over the four lines that the whole project turns on, and it says the one thing a reader must not get wrong about them:

    // Takes `quantity` from the stock only if that many are
    // left, in ONE statement, so two orders for the last
    // plate cannot both succeed. True if it was taken.
    async takeStock(conn, id, quantity) {

Both would be useless as descriptions. "Updates the stock" is what the code already says. What each comment carries instead is a constraint: keep this module pure, and keep this in one statement. Break either and the code still runs, which is exactly why it is written down.

Where comments belong, and where they do not:

Comment thisDo not comment this
why a choice was made, especially a surprising onewhat the next line does
a constraint the next reader must not breaka name that should be better instead
an outside rule the code obeys: a standard, a limit, a regulationcode you have commented out; delete it, git remembers
a unit or a format that is not in the name: paise, minutes since midnight, UTCa changelog in the file header; git has that too
a workaround, with what forced ityour name and the date on every function

A good name removes the need for a comment. takeStock needed one because its guarantee is invisible; stock_left, pickup_slot and price_paise needed none, because each says its own unit and meaning. When you find yourself writing a comment to explain a name, change the name.

File headers

Every file in this project opens with one or two lines saying what it is for and, where it matters, what it must not do. sql/seed.sql is the clearest case, because its header is a warning:

Canteen Pre-order: demonstration data. Every account below has the password canteen-demo. They exist for development and testing only: delete them, or change every password, before real students use the site.

That is three sentences that could save a real canteen from a real problem, in the file that creates it, where nobody can miss it.

The documents beside the code

A README cannot hold everything, and the answer is not a longer README but a note where the question arises:

WhereWhat it explains
deploy/README.mdwhat goes where on the server, and what holds no secret
data/README.mdwhere the common-password list came from, the commands that made it, and its licence
docs/uml/, docs/design/the diagrams, as PlantUML sources, so they can be redrawn rather than redrawn by hand
docs/plan/the plan: the WBS, the Gantt source and the critical-path script
sql/schema.sqlthe tables, with a comment on every choice a reader would question
.env.exampleevery setting, with a comment on each, and no real value
munotes.in459

Source Code Documentation

Two habits make this set worth having. Keep documentation as source where you can: the diagrams in this project are PlantUML text, so a change is a two-line edit and a redraw, and the drawing can never disagree with its source. And put the note next to the thing, not in a documentation folder far away: the person who needs to know about the password list is the person who has just opened data/.

Is JSDoc worth it?

For a library other people import, yes: the comment becomes the documentation of a function you cannot ask its author about. For an application of this size, with a test for every rule, a full JSDoc block over each function usually costs more than it returns, and the commonest result is a wall of comments that repeat the parameter names and rot at the first change.

The honest middle, which is what this project does: a line or two over anything whose contract is not obvious, and tests as the executable documentation of behaviour. If your college asks for JSDoc, generate it and keep it, but do not let it replace the comments that carry the why.

Do this for your project

  1. Write the README first, and keep it correct; a README that does not work is worse than none.
  2. Open it with the numbered commands that take a reader from clone to running, and say what success looks like.
  3. Name the traps you hit yourself, and warn about the seeded accounts.
  4. Give every folder that needs explaining its own short note.
  5. Comment the why, the constraints and the units; delete the rest.
  6. Improve names instead of explaining them.
  7. Keep diagrams as source, beside the code they describe.
  8. Have somebody else follow your README on their own machine before you submit it.

Mistakes that cost marks

No README, or one that stops at "npm install".

A README that does not work, usually because a step was done by hand once and never written down.

Comments that narrate the code: // loop through the items.

Commented-out code left in, sometimes several versions of it.

A changelog and an author's name in every file header, which git already holds and which nobody updates.

Passwords or keys in the repository, or in the README, other than a clearly warned demonstration password.

munotes.in460

Source Code Documentation

Documentation that contradicts the code, which is what happens when a diagram is drawn by hand.

Quick revision

  • Two halves: in the code (names, headers, comments) and about the code (README, folder notes, schema, API list).
  • The README keeps NFR-11: six numbered steps from clone to /api/health, and it says what success looks like.
  • Name the traps, and warn about the seeded accounts.
  • Comments carry the why, the constraints, the units; never what the code says.
  • A better name beats a comment.
  • A note beside the thing it explains: deploy/, data/, docs/.
  • Keep diagrams as source, so the drawing cannot disagree with it.
  • Somebody else follows your README before you submit.

Questions you must be able to answer

1. What does source code documentation consist of? Documentation in the code, which is its names, file headers and comments, and documentation about the code, which is the README, the folder-level notes, the schema and the API reference. The first serves whoever changes a file; the second serves whoever arrives at the project.

2. Why does the README matter more than its length suggests? Because it is the first thing an examiner or a new developer uses, and it either works or it does not. NFR-11 makes that measurable: a new developer runs the system within thirty minutes, which the six-step opening is written to achieve.

3. What makes a comment worth writing? That it says something the code cannot: why a choice was made, a constraint the next reader must not break, an outside rule being obeyed, or a unit that the name does not carry. A comment that describes the next line is noise, and it rots.

4. Give an example of a comment that had to exist in the worked project. The one over takeStock: it records that the stock is taken in one statement so that two orders for the last plate cannot both succeed. The code would still run if someone split it into a read and a write, which is precisely why the guarantee is written down.

5. Why keep diagrams as source rather than as pictures? Because a picture drifts from the system it describes and cannot be checked, while a source file can be edited, redrawn and compared. In this project the diagrams are PlantUML text in docs/, and the same source is printed in the chapters that discuss them.

6. Should every function have a JSDoc block? Not in an application of this size with tests for its rules; the blocks tend to repeat the parameter names and go stale. Document what is not obvious, let the tests document behaviour, and use full JSDoc where the code is a library for other people or where your college asks for it.

munotes.in461

Source Code Documentation

7. How do you know your source code documentation is good enough? Someone who has never seen the project clones it and reaches a running system from the README alone, without asking you anything, and can then say which file to change to add a dish.

Contents This chapter on its own page

munotes.in462

Chapter Seventy

The Final Deliverables: What Is Due at the End of Module 2

Syllabus topic Module 2, "Final Deliverables at the End of Module 2".

In one line

Four things are due, and they must all describe the same system: a working application, its GitHub repository, the final report, and a presentation with a demonstration.

In the wording to use when asked: the Module 2 deliverables are the working application, the version-controlled repository, the final project report with its appendices, and the presentation and demonstration; together they are assessed as 20 internal marks by the guide and 30 external marks by the examiner, with 40 per cent required in each.

The four, and where the marks are

DeliverableThe standard it is held toMarks it feedsChapter
The working applicationruns on the day, on the machine you brought, with data worth showingdemonstration 1071
The GitHub repositoryclones, installs and runs from its README; a history that shows four people workingtechnical design and implementation 1072
The final reportevery claim checkable, limitations listed, appendices completereport 573
Presentation and demonstrationtwelve minutes that answer what, why, how and what it costdemonstration 10, viva 5, and the guide's internal presentation 574

Read the third column again. The demonstration and the repository carry twenty of the external thirty, and the report carries five. That is not a reason to write a poor report, because the report is what the examiner reads first and what shapes every question afterwards; it is a reason not to spend the last week formatting a document while the application is still failing on the lab machine.

All four must describe one system. The commonest way a good project loses marks at the end is that the four drift apart: the report describes a screen the application no longer has, the repository's last commit is a fortnight older than the code being demonstrated, and the slides show a wireframe rather than the built page. Chapter 73 calls this the agreement check, and it is worth an hour of somebody's time.

What the submission set usually contains

MU names the deliverables and prints no format for handing them in, so the list below is common practice across colleges, not a syllabus requirement. Ask your guide in week 13, and write the answer down, because the answer differs between colleges and sometimes between guides:

ItemUsually
1The reportprinted and bound, one copy per student or one per group, plus a PDF
2A certificate pagesigned by the guide and the head of department, bound at the front
3A declaration of originalitysigned by every member
4The repositoryits link in the report, and the repository made visible to the examiner
5The applicationon a laptop, with the database and the seeded data, ready to run offline
6The installable appcanteen.apk on a phone, and a copy in the release (Chapter 62)
7The appendicesthe user manual, the screenshots, the full test cases, the schema
8A soft copya folder or drive holding the PDF, the code and the APK
munotes.in463

The Final Deliverables: What Is Due at the End of Module 2

Three questions to ask with them, because each has cost somebody a day: how many printed copies, whose signatures on the certificate, and whether the examiner sees the repository or a soft copy. A private repository that the examiner cannot open is the same as no repository at all; Chapter 72 says what to do about it.

The last two weeks, planned backwards

Plan the end from the examination day, not from today, because every item below has to be finished before the one after it can be done properly. The worked team's calendar, with the day the college fixed the examination called E:

Days before EWhat happensWhy here
114Code freeze. Nothing new is built. Only defects that stop a demonstration are fixed.everything after this describes the code, so the code must stop moving
213The repository is tidied: branches merged and deleted, issues closed, the release taggedthe report cites a version; that version must exist
312The whole suite is run on a clean clone, following the READMEproves the repository, not your own machine
411Screenshots taken, last, from seeded data (Chapter 68)they must show the frozen code
510 to 6The report is assembled, read by every member, and its agreement with the code checkedthe longest task, and it needs the screenshots
66Printing and binding beginsbinders are closed on Sundays and busy at the end of term
75The demonstration is rehearsed on the machine that will be used, twicethe first rehearsal always finds something
84The offline fallback is prepared and tested: local database, seeded data, the APK already installedthe network is the thing most likely to fail
93The presentation is rehearsed to time, with the questions each member will taketwelve minutes is shorter than it sounds
102Everything is packed: report, laptop, charger, phone, adapter, soft copynot a joke: a missing adapter has ended a demonstration
111A last full run of the demonstration from a cold start, and then stopconfidence, and nothing new to break
120The examination

In the worked plan these two weeks are the task called R, 16 to 26 October, with the submission on Monday 26 October and nine working days of slack behind it against the end of week 15, Friday 6 November (Chapter 17).

munotes.in464

The Final Deliverables: What Is Due at the End of Module 2

The code freeze is the decision that makes the rest possible. Everything in the list after it describes the system: screenshots, report, rehearsal, fallback. Each time the code changes, every one of them is stale again. A team that keeps building until three days before the examination submits a report about a system nobody has, and it will be found, because the examiner asks the system to do what the report says.

What is not due

Chapter 1 sets this out and it is worth one line here, because students lose days to it: MU prints the journal, the 80 per cent completion rule and the two-hour practical paper for the other practical courses, not for Mini Project I. There is no question paper and no certified journal to prepare. What is examined is your project and your command of it.

Do this for your project

  1. Write the four deliverables on one page in week 12 and name who owns each.
  2. Ask your guide what the submission set is, and how many copies, and write the answer down.
  3. Fix the code freeze date two weeks before the examination, and hold it.
  4. Do them in the order above: freeze, tidy the repository, clean clone, screenshots, report, binding, rehearsal, fallback.
  5. Check the four against each other: the report, the code, the screenshots and the slides must describe one system.
  6. Make the repository openable by the examiner, or agree the alternative with your guide.
  7. Pack the day before, and run the demonstration once from a cold start.

Mistakes that cost marks

Building until the last week, so the report, the screenshots and the slides describe three different systems.

A repository the examiner cannot open, which counts for nothing.

Finding out the submission format in the last week, after the binder has closed.

Skipping the clean clone, and discovering on the day that the project runs only on one laptop.

No offline fallback, and a demonstration that depends on the college Wi-Fi.

The wrong version demonstrated, because there is no tag and nobody is sure what was submitted.

Quick revision

  • Four deliverables: working application, GitHub repository, final report, presentation and demonstration.
  • External 30: demonstration 10, technical design and implementation 10, report 5, viva 5; internal 20 by the guide; 40 per cent needed in each.
  • All four must describe one system, and that is checked, not hoped for.
  • MU prints no submission format: ask your guide about copies, signatures and how the examiner sees the repository.
  • Plan backwards from the examination: freeze at E minus 14, then repository, clean clone, screenshots, report, binding, rehearsal, fallback, pack, cold-start run.
  • No journal, no 80 per cent rule, no question paper for this paper.
munotes.in465

The Final Deliverables: What Is Due at the End of Module 2

Questions you must be able to answer

1. What are the four deliverables at the end of Module 2? The working application, the project's GitHub repository, the final report with its appendices, and the presentation with a demonstration of the system.

2. How are the marks divided, and what does that imply about where the last fortnight goes? Twenty internal marks by the guide and thirty external by the examiner: demonstration 10, technical design and implementation 10, report 5, viva 5, with 40 per cent needed in each. Twenty of the external thirty rest on the system running and the code being readable, so the last fortnight protects the demonstration and the repository first, and the report is assembled rather than written from nothing.

3. Why is a code freeze the first item in the last two weeks? Because everything else describes the code. Screenshots, the report, the slides, the rehearsal and the fallback are all made from the system as it stands, and each change makes them stale again.

4. What does MU say about the format of the submission? Nothing: she names the deliverables and prints no page count, binding, certificate or soft-copy requirement. The certificate page, the declaration and the number of printed copies are college practice, so they are asked about early and written down.

5. Why run the tests on a clean clone rather than on your own machine? Because the repository is a deliverable and is marked as one. A clean clone run from the README is the only thing that proves the project works for somebody who is not you, and it is how the six-step check of Chapter 39 is used at the end.

6. What does "the four must describe one system" mean in practice? That the report's screens, the screenshots, the slides and the demonstrated application are the same version, and that the version is tagged in the repository so it can be named. Any drift between them is the examiner's first and easiest question.

Contents This chapter on its own page

munotes.in466

Chapter Seventy-One

The Working Application

Syllabus topic Module 2, "Final Deliverables at the End of Module 2: Working Application".

In one line

To an examiner, "working" means it starts from cold on the machine you brought, does the whole job with believable data in front of them, says something useful when it is given nonsense, and does not depend on anything outside the room.

In the wording to use when asked: the working application is the deployed, executable system presented for demonstration; it must start from a cold state on the demonstration hardware, exercise every main use case with representative data, handle invalid input visibly, recover from a restart, and run without dependence on any external network or service.

What "working" means to an examiner

Five tests, and a project that passes all five cannot have a bad demonstration:

  1. It starts from cold. The laptop is switched on in front of them, and three minutes later the system answers.
  2. It does the whole job. A student orders, the counter serves, the kitchen sees the list, the owner reads the takings. Not one screen: the loop.
  3. It refuses nonsense visibly. Ask for three of a dish with two left and a sentence appears that a student could act on (Chapter 64).
  4. It survives a restart. Stop it, start it again, and the order placed a minute ago is still there. That is the difference between a program and a system.
  5. It needs nothing from outside the room. No internet, no cloud account, no borrowed key.

Nothing on that list is about how much was built. An examiner cannot see the size of your project; they can only see whether what is in front of them works.

The readiness checklist

Run this two days before, on the machine you will bring, and write the answer beside each line. Every expected answer below is the project's own, printed by its own code:

CheckHowExpected
1The six steps work on a clean cloneclone into a new folder and follow the READMEstep 6 answers that both the application and its database are ok
2The suite passesnpm test117 tests, 0 failed
3The layers holdnpm run layersno layer reached past
4Every role signs inthe three seeded accounts, password canteen-demothe student sees the menu, the counter the orders, the owner both and the report
5The whole loop runsplace, prepare, ready, collectthe order ends as Collected and the report's total rises
6A refusal is readableask for three of a dish with two left"Only 2 Chicken Biryani left."
7It survives a restartstop the server, start it, reloadthe order is still there, still in the same state
8The database can be down and say sostop MySQL, ask /api/health503, and the database reported unreachable
9The port is freestart it twiceone sentence naming the port and what to do about it
10The app is on the phoneopen canteen.apk, sign in, orderthe same pages, on the phone, against your server
11Nothing is fetched from the internetdisconnect the network and reload every pageevery page and style still renders
12The clock can be fixedset DEMO_TIME, restartthe server prints that the clock stands still, at that instant
munotes.in467

The Working Application

Three of those answers are worth having in front of you, in the application's own words. Check 1 and check 8 are the health route's two answers, {"status":"ok","database":"ok"} and, with 503, {"status":"down","database":"unreachable"}; the second tells you in one second that the application is up and MySQL is not. Check 9 answers:

Port 3000 is already in use. Stop whatever is using it, or set PORT in .env to another number.

Check 9 exists because of what writing this chapter found: the start-up code had a friendly message for a port in use that could never be printed, because listen's callback is never handed an error. The fix is four lines, and the checklist is what would have caught it on the day.

Check 11 is the one students assume and should prove. In this project it holds by design: there is not one script, stylesheet or font fetched from anywhere, so the pages render with the network cable out. A project that loads a stylesheet or an icon set from the internet has a demonstration that depends on the college Wi-Fi, and it will be the Wi-Fi that fails, not the project.

Data worth demonstrating with

The seeded data of sql/seed.sql exists for this: fourteen dishes with real names, real prices in paise and sensible stock, three roles and six people. Compare a demonstration with that against one with item1 at Rs 1 and users a@a.com and test, and the difference is a mark or two for nothing.

Four rules for demonstration data:

  • Believable. Real dish names, real prices, plausible quantities. It takes ten minutes and it makes the system look finished.
  • Enough of it, and not too much. A menu of fourteen and a handful of orders shows structure; two hundred rows shows nothing and scrolls.
  • Never a real person's. Reset to the seeded accounts before the day; no real student's name, address or order in front of an audience.
  • Set up so the interesting cases exist. One dish already sold out, one down to two, one switched off for the day: that is how the refusals can be shown without pretending.

And one that only a project with a clock needs. The counter's screens exist during the lunch break, and an examination is rarely at 12:35, so DEMO_TIME in .env stops the clock where you need it (Chapter 68 has the three instants). The server prints at start-up that the clock stands still, so nobody demonstrates with a frozen clock by accident, and you can say out loud that it is set, which is more impressive than hoping nobody notices.

munotes.in468

The Working Application

When the network fails, and it will

The rule is simple: the demonstration runs entirely on the machine in your hands. Server, database and browser on one laptop, reaching each other on 127.0.0.1, exactly as the six steps set it up. Nothing in the room matters except the laptop and its charger.

The phone is the only part that needs two machines to talk. Three ways, in order of reliability:

HowWhat it costs
1Show the phone pages in the laptop's browser at phone widthnothing, and it needs no phone, but it is not the app
2The laptop makes a hotspot; the phone joins it; the APK is built for the laptop's address on that networkten minutes the day before, and a rebuild if the address changes
3The phone makes a hotspot; the laptop joins it; same rebuilduses your data allowance for nothing, but it works where a laptop cannot share

Whichever you choose, do it the day before and leave it set up. The APK's address is fixed when it is built (SERVER_URL in android/app/build.gradle.kts), so discovering on the day that it points at the lab desktop means rebuilding an APK in front of an examiner.

Two more things in the bag: a USB drive with the whole project, the PDF and the APK, in case the laptop dies and a friend's machine must be used, and a printed page with the seeded accounts and the demonstration steps, so that nobody has to remember a password while being watched.

The cold start, as a drill

Practise this until it takes three minutes and nobody has to think:

sudo systemctl start mysql              # or the local MySQL service
cd ~/canteen-preorder
npm start                               # the server says where it is running
curl localhost:3000/api/health          # {"status":"ok","database":"ok"}

Then, in the browser: sign in as the owner, check the day's stock is set, sign in as a student in a second window, and place one order to prove the loop before anybody is watching. If you have to reset, npm run db:setup puts the seeded data back in seconds, which is why nothing in the demonstration should depend on data you cannot recreate.

munotes.in469

The Working Application

When something breaks in front of the examiner

It happens to good projects, and how you handle it is itself assessed, because it shows whether you understand your own system.

  • Say what you expected. "That should have moved to Ready; the list refreshes every ten seconds, so let me reload it." An examiner who hears the mechanism knows you built it.
  • Give it one minute. Beyond that, move on to the next part of the demonstration and come back. Nobody's marks improve while four students crowd a keyboard.
  • Show the log if it helps you. The server prints a line for every request, including every refusal; being able to point at it is evidence, not an apology.
  • Never blame the machine without checking. Half of all demonstration failures are the network, and you have removed that dependency; the other half are the two mistakes in the checklist above, which is why they are on it.

Do this for your project

  1. Decide the demonstration machine and prepare only that one.
  2. Run the twelve checks two days before, on that machine, and write the answers down.
  3. Seed believable data, and set up the interesting cases so refusals can be shown honestly.
  4. Fix the clock if your system depends on the time of day, and say so out loud.
  5. Remove every dependency on the network: nothing fetched from the internet, database local.
  6. Prepare the phone the day before, and leave it set up.
  7. Practise the cold start until it takes three minutes.
  8. Carry a USB drive with everything, and a printed page of accounts and steps.

Mistakes that cost marks

A demonstration that needs the Wi-Fi, usually because a stylesheet or font is loaded from the internet.

Data that looks like a test: item1, a@a.com, prices of Rs 1.

A first start in front of the examiner, and a start-up error nobody has seen before.

No way to reset, so a mistake in the first minute spoils everything after it.

The APK pointing at the college lab's address, demonstrated from a room on the other side of the college.

Demonstrating from a laptop that has never run the clean clone, so the system works only where it was written.

Four people at one keyboard when something fails.

Quick revision

  • Working means: starts from cold, does the whole loop, refuses nonsense visibly, survives a restart, needs nothing outside the room.
  • Run the twelve checks two days before, on the machine you will bring.
  • The health route answers 503 and "database":"unreachable" when MySQL is down; a second start says the port is already in use.
  • Believable seeded data, the interesting cases prepared, never a real person's.
  • DEMO_TIME fixes the clock, and the server says at start-up that it is fixed.
  • Everything on one laptop; the phone joins a hotspot, prepared the day before.
  • Practise the cold start; carry a USB drive and a printed page of accounts and steps.
  • When it breaks: say what you expected, one minute, move on.
munotes.in470

The Working Application

Questions you must be able to answer

1. What does an examiner mean by a working application? One that starts from cold on the machine you brought, carries out the whole main path with believable data, refuses invalid input with a message a user could act on, keeps its data across a restart, and depends on nothing outside the room.

2. How do you prove the application is not dependent on the network? By disconnecting it and reloading every page. In this project it holds because nothing is fetched from the internet: every script, stylesheet and image is served by the application itself, and the database is on the same machine, reached on 127.0.0.1.

3. Why does the health route matter at a demonstration? Because it separates two failures that look the same. {"status":"ok","database":"ok"} says the application is up and can reach its database; a 503 with "database":"unreachable" says the application is up and MySQL is not, which is a thirty-second fix rather than a mystery.

4. Why fix the clock with DEMO_TIME, and why announce it? Because the counter's screens exist only during the lunch break and an examination is rarely at 12:35. It is announced because the server prints it at start-up and because saying so is honest: a demonstration at a fixed instant shows the system, and pretending otherwise is the thing that looks bad.

5. What data should a demonstration use? The seeded demonstration data: real dish names and prices, sensible quantities, the three roles, and the interesting cases already set up, one dish sold out and one nearly so. Never a real person's data, and never so much of it that the screens are just scrolling.

6. What do you do when something fails while the examiner is watching? Say what you expected and why, give it about a minute, use the log if it helps, and then move on and return to it. How a failure is handled shows whether the team understands the system it built.

Contents This chapter on its own page

munotes.in471

Chapter Seventy-Two

The GitHub Repository

Syllabus topic Module 2, "Final Deliverables at the End of Module 2: GitHub Repository".

In one line

The repository is the only deliverable an examiner can inspect without you in the room, and it answers three questions at a glance: does it run, did the whole team build it, and is anything in it that should not be.

In the wording to use when asked: the repository is the version-controlled record of the project, presented as a deliverable; it is assessed on whether the working system can be obtained and run from it, on what its commit history shows about how the work was done and by whom, and on the absence of secrets, generated files and material that does not belong in version control.

What an examiner opens, in order

They look atIt tells them
1the READMEwhether this can be run at all, in the first minute (Chapter 69)
2the file listwhether the project is organised, and whether .env or a key is sitting there
3the commit historywhether four people worked, over weeks, or one person worked over one weekend
4the contributorsthe same question, counted
5the issueswhether defects were tracked or remembered (Chapter 56)
6the pull requestswhether anybody read anybody else's code (Chapter 62)
7the releases and tagswhich commit the report and the demonstration describe

That order matters. The README decides the mood of everything after it, which is why Chapter 69 spends a chapter on a file that takes an hour to write.

The history is the evidence, and it cannot be faked at the end

A commit history records dates, sizes and authors, and those three together say how a project was built. An examiner does not need to read the diffs to see:

  • four people or one. Four names, each with commits in their own area and some outside it.
  • weeks or a weekend. Commits spread through September and October, or two hundred on 24 October.
  • built side by side or in sequence. The worked history alternates between public/ and src/ from 11 September, which is what the guide remarked on at the code review without being told (Chapter 49).
  • tested as it went. Tests appearing in the same commits as the code they test, not in one commit at the end.

Nothing here can be arranged in the last week, and that is the point. Commit as you work, and the evidence writes itself.

Commits must carry real names and emails

Chapter 39 set user.name and user.email on each machine, once, and this is where that pays. If a member never set them, their commits are attributed to whatever their computer's account happens to be called, and the repository then shows three contributors, or four names that nobody can match to the team.

munotes.in472

The GitHub Repository

git shortlog is the command that answers it, and it is what GitHub's contributors list is built from:

$ rm -rf ~/who-wrote-it; mkdir ~/who-wrote-it && cd ~/who-wrote-it
$ git init -q
$ git -c user.name="Aditi Kulkarni" -c user.email="aditi@college.example" \
    commit -q --allow-empty -m "Add the README"
$ git -c user.name="Farhan Shaikh" -c user.email="farhan@college.example" \
    commit -q --allow-empty -m "Take an order's stock in one update"
$ git -c user.name="Sneha Patil" -c user.email="sneha@college.example" \
    commit -q --allow-empty -m "Draw the counter's list"
$ git -c user.name="Rohan Deshmukh" -c user.email="rohan@college.example" \
    commit -q --allow-empty -m "Limit failed sign-ins"
$ git -c user.name="Farhan Shaikh" -c user.email="farhan@college.example" \
    commit -q --allow-empty -m "Give back a cancelled order's stock"
$ git --no-pager shortlog -sne HEAD
     2	Farhan Shaikh <farhan@college.example>
     1	Aditi Kulkarni <aditi@college.example>
     1	Rohan Deshmukh <rohan@college.example>
     1	Sneha Patil <sneha@college.example>

Two things to read in that answer: every author is a person, with a college address, and the counts are not one person's project with three names attached. Run it on your own repository in week 13, and if a name is wrong, fix the setting on that machine and say so; a wrong name cannot be corrected in the history without rewriting it, which is worse.

Say HEAD, as that command does. With nothing to count, git shortlog reads its standard input, expecting git log's output to be piped in, and in a script or anywhere without a terminal it therefore prints nothing at all and looks like a repository with no authors. It was written here without HEAD first, and that is exactly what it did.

Nothing in it that should not be there

This is the part that costs marks, and occasionally more than marks:

Must never be committedWhyWhere it is handled
.envit holds the database password.gitignore, printed in Chapter 39
a keystore, .jks, a signing keywhoever has it can sign an app as you.gitignore, and Chapter 59
node_modules/thousands of files that npm ci recreates exactly.gitignore
build output: android/build/, app/build/, *.loggenerated, large, and always stale.gitignore
a database dump with real people in itit is their data, not yoursseeded data instead (Chapter 68)
a password or key pasted into codeit is in the history for eversettings from the environment (Chapter 28)
screenshots of somebody's messages, a CV, a resume, holiday photosit happens, from git add .add by path, never by dot

Three commands to run before you submit, on your own repository:

git ls-files | grep -Ei '(^|/)\.env$|\.jks$|\.keystore$|^node_modules/'
git log --all --diff-filter=A --name-only --format= | sort -u | grep -Ei 'secret|password|\.env|key'
git count-objects -vH | grep size-pack
munotes.in473

The GitHub Repository

The first lists forbidden files that are tracked now; the second lists every file ever added in the history, which is where a secret committed in week 3 and deleted in week 4 still lives; the third says how large the repository is, and a size in tens of megabytes usually means node_modules or a build folder went in at some point.

If a password was ever committed, change the password. Deleting the file does not remove it from the history, and rewriting history on a shared repository breaks everybody's clone. Changing the secret is the only fix that works, and saying so in the report's limitations is better than hoping.

Making it openable by the examiner

A repository the examiner cannot open counts for nothing, and this is a decision, not an accident. The worked repository was private all semester, with the four members as collaborators, which is the right default for student work. Before the examination the team had three choices:

ChoiceWhat it means
1Make it publicanyone can read it; simplest, and what most colleges expect. Check first that no secret was ever committed
2Add the examiner as a collaboratorneeds their GitHub account name, which you rarely have in advance
3Keep it private and show it from your laptopworks in the room, proves nothing afterwards

Ask your guide which the college wants, in week 13, with the other submission questions (Chapter 70). Whatever you choose, put the repository's address in the report, on the certificate page or the first page, where it cannot be missed.

The release, and what the tag is for

Chapter 62 tagged the submitted version v1.0 and published it as a release with the APK attached. Two reasons that is worth the five minutes:

  • The report describes one version, and the tag names it. Without a tag, "the submitted code" is whatever main happens to be when the examiner looks, which may be three commits later.
  • The examiner can download the app that was demonstrated, rather than trusting that the APK on your phone came from this code.

The repository review, before you submit

CheckExpected
1Clone into an empty folder and follow the READMEa running system, and /api/health answers
2npm test on that clone117 tests, 0 failed
3git shortlog -sne HEADevery member, with a real name and a college address
4git log --oneline --format='%ad %an' --date=shortwork spread across weeks, not one night
5Branchesmerged and deleted; nothing half-finished left on main
6Issuesthe defects you found, closed, with the commit that fixed each
7Pull requestsreviewed by somebody other than the author
8The forbidden-file commands abovenothing found
9Tag and releasev1.0 on the submitted commit, with the APK attached
10The README's link and the report's linkthey point at each other
munotes.in474

The GitHub Repository

Do this for your project

  1. Create the repository in week 1, private, with the whole team as collaborators.
  2. Set user.name and user.email on every machine before the first commit.
  3. Commit as you work, in small commits that name what changed; never git add ..
  4. Keep .gitignore honest from the first day; .env and keys never go in.
  5. Use branches and pull requests, so the history shows that code was read.
  6. Track defects as issues and close them with the commit that fixes them.
  7. Run the ten-line review above before you submit, and the three forbidden-file commands with it.
  8. Tag the submitted version, publish a release, attach the APK, and put the address in the report.

Mistakes that cost marks

One member committing everything, because the others sent their files by chat.

Two hundred commits on one day, all named "update".

git add ., which is how .env, a keystore and somebody's photographs get committed.

node_modules/ in the repository, which makes it enormous and says the team did not know what a lock file is for.

A private repository with no way in, discovered on the examination day.

No tag, so nobody can say which commit the report describes.

A README that does not work, which undoes the good impression of everything else.

Quick revision

  • An examiner reads, in order: README, file list, history, contributors, issues, pull requests, releases.
  • The history shows how many people, over how long, and whether tests came with the code; it cannot be arranged at the end.
  • git shortlog -sne HEAD is the contributors list: every member, real name, college address (git config, Chapter 39); without HEAD it reads standard input and prints nothing.
  • Never committed: .env, keys and keystores, node_modules/, build output, real people's data, secrets in code.
  • A secret ever committed means change the secret; deleting the file is not a fix.
  • Decide how the examiner opens it, and put the address in the report.
  • Tag the submitted commit, publish a release, attach the APK.

Questions you must be able to answer

1. Why is the repository assessed and not just the code inside it? Because it is the only deliverable that can be inspected without the team present, and because how the work was done is part of what is being taught: the history shows who built what, over what period, and whether code was read and defects tracked.

munotes.in475

The GitHub Repository

2. What can an examiner tell from a commit history without reading a single diff? How many people worked, over how many weeks, in which parts of the system, whether tests arrived with the code they test, and whether the work was continuous or done in one burst at the end.

3. Why must every machine set user.name and user.email before the first commit? Because those two values are recorded in every commit and are what the contributors list is counted from. A member who never set them appears under whatever their computer's account is called, or not as themselves at all, and the history cannot be corrected afterwards without rewriting it.

4. What must never be in the repository, and what do you do if a password was committed? Settings files with real passwords, signing keys and keystores, installed packages, build output, real people's data, and secrets pasted into code. If a password was ever committed, change the password: it remains in the history even after the file is deleted, and rewriting a shared history breaks every clone.

5. Why tag the submitted version? So that the report, the demonstration and the code can be shown to be the same thing. Without a tag, "the submitted code" is whatever the main branch holds when somebody looks.

6. How should the examiner get access to a private student repository? By a decision made in advance with the guide: make it public after checking that no secret was ever committed, or add the examiner as a collaborator, and either way print its address in the report. Leaving it private with no arrangement is the same as having no repository.

Contents This chapter on its own page

munotes.in476

Chapter Seventy-Three

The Final Report

Syllabus topic Module 2, "Final Deliverables at the End of Module 2: Final Report".

In one line

The final report is assembled, not written: the documents already exist, and the work of the last fortnight is making them one document, checking that it agrees with the system, and handing it in on time.

In the wording to use when asked: the final report is the bound, submitted account of the project, assembled from the documents produced during the project, with front matter, numbered sections, figures and tables, references and appendices; it is checked for internal consistency and for agreement with the delivered system before submission.

The worked report's contents page

This is the whole of it, with the page counts the sections actually took. Print your own like this: an examiner uses it to find things, and its shape alone says whether a project was managed.

SectionPagesCame from
Title page, certificate, declaration, acknowledgement, contents, list of figures, list of tables, abstracti to viiiwritten last, except the abstract, written twice
1Introduction: the problem, the evidence, the objectives, the scope3Chapters 3 to 5
2Feasibility and planning: technical, economic, operational; the SDLC model; the plan against what happened3Chapters 6 to 8, 15 to 18
3Requirements: user classes, FR-1 to FR-17, NFR-1 to NFR-12, constraints, assumptions5Chapter 34
4System design: architecture and its decisions, the six UML diagrams, the schema, the API, security6Chapters 35, 36
5Implementation: the stack, the layers, deployment, the Android app, what was hard5Chapters 39 to 49, 57 to 60
6Testing and verification: plan, levels, results, traceability, load, input, security4Chapters 50 to 56, 63 to 65
7Results: each objective answered as far as the evidence goes1Chapter 66
8Limitations1Chapter 66
9Conclusion and future work1Chapter 66
References1the sources, with versions and dates
AThe feasibility report2Chapter 8
BThe test cases, all 264Chapter 55
CThe screenshots, 12 with captions3Chapter 68
DThe user manual11Chapter 67
EThe database schema and the API3Chapters 29, 30

Thirty numbered pages and twenty-three of appendices, fifty-three in all, with eight pages of front matter before them. The appendices are almost half of it, and every one of them was finished before the report was assembled. That is what "assembled, not written" means in numbers.

Assembling it without losing a week

One document, not four. Four members each writing their own chapter in their own file produces a report in four voices with four numbering schemes and three different names for the same thing. The worked team used one document, owned by Aditi, with the others writing into it in agreed sections and Aditi editing for one voice at the end. If you must work in separate files, agree the styles, the figure numbering and the names on day one, and leave a whole day for the merge.

munotes.in477

The Final Report

Number everything, and refer by number. Figure 4.3, Table 6.2, section 3.5, FR-11, NFR-1, ADR-5. A report whose text says "as shown in the diagram above" cannot survive a page break, and page breaks move when anything is edited.

Use the styles, not the ruler. Heading 1, Heading 2, body, caption: then the contents page, the list of figures and the list of tables are generated and correct. Hand-formatted headings mean a contents page that is wrong by the second edit, and every student who has typed one twice knows it.

Write the abstract last, and twice. Half a page: the problem, what was built, what was measured, what remains. The first version is written after the report and the second after somebody else reads it and cannot tell you what the project does.

Originality and citations

Two different obligations, and students usually meet neither.

Cite what you used. Every standard, source, tool and library that the report leans on, with the version and the date you read it, because a web page today is not the page of six months ago. The worked report's references, in the form the book's own sources file keeps them:

OWASP, Password Storage Cheat Sheet, OWASP Cheat Sheet Series, read 30 September 2026.

W3C, Web Content Accessibility Guidelines (WCAG) 2.2, W3C Recommendation, read 30 September 2026.

NIST, SP 800-63B-4, Digital Identity Guidelines: Authentication, read 30 September 2026.

OWASP, Application Security Verification Standard 5.0, chapter V6 Authentication, read 30 September 2026.

OWASP, Top 10, 2025, read 30 September 2026.

Michael Nygard, Documenting Architecture Decisions, 15 November 2011.

Peter Pin-Shan Chen, The Entity-Relationship Model: Toward a Unified View of Data, ACM Transactions on Database Systems 1(1), March 1976, pages 9 to 36.

ISO/IEC/IEEE 42010:2022, Software, systems and enterprise architecture description, second edition, November 2022, catalogue entry read 30 September 2026.

Agile Business Consortium, MoSCoW Prioritisation, read 30 September 2026.

Oracle, MySQL 8.0 Reference Manual, and the MySQL product lifecycle page, read 30 September 2026.

Notice the last line of several: read on a date. And notice what the ISO entry says: the catalogue entry was read, not the standard, because the standard is sold. Say what you read, not what you would like to have read; an examiner who asks "what does 42010 require of an architecture description?" is asking exactly this.

Cite code you did not write, in the file and in the report: a function copied from an answer on a forum, a stylesheet from a tutorial, a licensed word list. This project has one such item, the common-password list, and data/README.md records where it came from and under what licence (Chapter 46). A library installed with npm needs no citation beyond the lock file; code pasted into your own files does.

munotes.in478

The Final Report

Originality is about the numbers. Copying another group's report is obvious and fatal, and it is not the interesting case. The interesting case is a report that says "the average wait fell to 4 minutes" when nobody measured it, or a limitations section copied from a template, or a chapter of smooth prose that nobody in the group can explain. All three are found in the same way: the examiner asks how you know. Every number in the report must be one you took, and every sentence one a member can defend at the viva (Chapter 76).

The declaration you sign says the work is your own. Signing it while a section is not is worse than a low mark.

The checks before it is bound

Print this page, tick it, and do the ticking with the code and the screenshots open beside you:

CheckWhy it is here
1The report, the code, the screenshots and the slides describe the same versionthe drift of Chapter 70, and the examiner's easiest question
2Every figure and table is numbered, captioned and referred to by numbera caption nobody refers to is decoration
3Every requirement is traceable to a test or a screenshotsection 6 is where an examiner checks for gaps (Chapter 55)
4Every number says where it came from and under what conditionsChapter 66's rule; the viva tests it
5The limitations section is present and honestan examiner finds them either way
6Future work matches what was prioritised and not builtChapter 13's record
7The contents page, the list of figures and the list of tables are regeneratedthey are wrong after any edit
8Page numbers run, and no section starts mid-page by accident
9The repository's address is in the front matter, and it opensChapter 72
10Names, roll numbers, the college's name and the guide's name are spelled correctlythe one mistake nobody forgives
11The certificate and the declaration are signedchase signatures a week early
12Somebody who did not write a section has read itfour writers, one voice
13The PDF has its fonts embedded, and prints as it looksa report printed with substituted fonts loses its tables
14A spare printed copy and the PDF on a drivebinders and printers fail on the last day
munotes.in479

The Final Report

Two of those are worth a sentence each. Check 1 is the one that fails, because the code moved after the report was drafted; that is why the code freeze is fourteen days before (Chapter 70). And check 11 needs other people's time: a guide who is away for three days can cost you the deadline, so ask in week 13.

Binding, copies and the deadline

MU prints no format, so this is your college's to answer, and Chapter 70's three questions are the ones to ask: how many printed copies, whose signatures, and whether the examiner reads the repository or a soft copy. Common practice is spiral binding for a mini project and hard binding for a final-year project, one copy for the department and one per student, plus a PDF; but common practice is not a rule, and the only safe source is your guide.

Two practical notes that cost nothing and save a day. Print two days early, because a printer that jams on the last afternoon is a story every teacher has heard. And submit the PDF by email as well if that is allowed, so that there is a timestamp of the version you handed in.

Do this for your project

  1. Keep every document as you produce it; the report is their assembly.
  2. Work in one document, with styles, and generate the contents page.
  3. Number every figure, table and section, and refer to them by number.
  4. Cite every standard and source with its version and the date you read it, and say what you read.
  5. Cite code you did not write, in the file and in the report.
  6. Make sure every number is one you took, and every section defensible by a member.
  7. Write the abstract last, and again after somebody else reads it.
  8. Run the fourteen checks with the code open, a week before the deadline.
  9. Chase signatures early; print two days early; keep a spare copy and the PDF.

Mistakes that cost marks

A report assembled in one night, in four voices, with three names for the same screen.

A contents page typed by hand, wrong by the second edit.

"As shown above", after the layout moved.

Sources with no version and no date, or a claim about a standard nobody read.

A number nobody measured, which the viva finds in one question.

An unsigned certificate, or a guide asked for a signature on the last day.

The college's or the guide's name misspelled.

A PDF whose fonts were not embedded, printed with different ones.

Quick revision

  • The report is assembled from documents that already exist; the appendices are almost half of it.
  • Contents page: front matter, nine numbered sections, references, appendices A to E; 30 + 23 = 53 pages.
  • One document, one voice, styles, generated contents; number everything and refer by number.
  • Cite with version and date, and say what you actually read; cite code you did not write.
  • Every number is one you took; every section defensible at the viva.
  • Fourteen checks before binding, the first being that report, code, screenshots and slides are one version.
  • MU prints no format: copies, signatures and soft copy come from your guide.
munotes.in480

The Final Report

Questions you must be able to answer

1. Why is the final report described as assembly rather than writing? Because the proposal, feasibility report, SRS, UML set, architecture document, test plan and reports, user manual and screenshots were all produced as the work was done. What remains is the introduction, results, limitations and conclusion, and the editing that makes many documents one.

2. What does a contents page tell an examiner before they read anything? Whether the project was managed: whether design, implementation and testing each have their own substantial section, whether results and limitations exist at all, and whether the appendices hold the evidence the text will lean on.

3. How should a source be cited in a report like this? Author or issuing body, title, version or edition, and the date you read it, because online standards and pages change. If you read a catalogue entry rather than the standard itself, say so: it is what you can defend.

4. What counts as a failure of originality here, beyond copying another report? A number nobody measured, a section copied from a template, or prose no member can explain. All three are found by the same question at the viva, which is how you know.

5. Which of the pre-binding checks fails most often, and why? That the report, the code, the screenshots and the slides describe the same version. The report is drafted while the code is still moving, which is why the code freeze comes first in the last fortnight.

6. What should you ask your guide, and when? In week 13: how many printed copies, whose signatures the certificate needs, and how the examiner will see the repository. All three need other people's time, and none of them is printed in the syllabus.

Contents This chapter on its own page

munotes.in481

Chapter Seventy-Four

Presentation and Demonstration

Syllabus topic Module 2, "Presentation & Demonstration", and Module 1's "Internal Presentation / Review".

In one line

Two events, not one: a presentation to your guide who has watched the project all semester, and a demonstration to an examiner who has never seen it, and the second begins with the problem because they do not know it.

In the wording to use when asked: the internal presentation is a review before the project guide of what was planned, built and learned; the external demonstration is a timed exhibition of the working system before an examiner, structured to evidence the requirements, the design decisions and the verification performed, and followed by questions.

The two audiences

The guide's reviewThe examiner's demonstration
Knowsthe project, the plan, your teamnothing at all
Wantsprogress against the plan, and honesty about what slippedthat the system exists, works, and was designed
Starts withwhere we are against the planthe problem, in one sentence with a number in it
Marks5, internal10, external, with 5 for the viva
Lengthas your guide sets itas your college sets it; plan for twelve minutes

The commonest mistake is giving the guide's talk to the examiner. "We finished the counter screen last week and the load test is pending" means nothing to somebody who does not know what a counter screen is. The examiner's first minute has to establish a problem worth solving.

The slides: ten, and no more

Twelve minutes of presentation and demonstration together, planned as five minutes of slides, six of the system, one to close:

SlideSaysSeconds
1Titlethe project, the four names, the guide, the college20
2The problem212 students served a day, 31.2 walking away, 16 minutes in a 40-minute break45
3What we builtone screenshot of the menu page, and one sentence30
4Scopefour pickup slots, cash at the counter; not payment, not delivery25
5Requirements17 functional, 12 non-functional, prioritised: all Musts and all Shoulds built30
6Architecturethe one diagram: two clients, the layers, MySQL45
7The hard partthe stock rule, and why two orders for the last plate cannot both succeed45
8Testingunit, black box, integration, load, security; 117 tests; the defect a person found30
9Results and limitationsthe objectives answered, and the limits we accepted20
10Future workthe three Could Haves and the two fixes we would make first10

Five minutes, three hundred seconds, and slide 7 is the one that earns the design marks. Every project has a hardest part; the team that can name theirs, explain it in forty-five seconds and show the four lines of SQL that solve it has said more about its engineering than ten slides of screenshots could.

munotes.in482

Presentation and Demonstration

Four rules for the slides themselves:

  • One idea to a slide, and a title that states it: "Two orders for the last plate cannot both succeed", not "Implementation".
  • No paragraphs. If a slide has to be read, it will be read instead of you being listened to.
  • Every diagram is one you drew and can explain line by line, from the UML set (Chapter 35).
  • Screenshots, not promises: slide 3 shows the built page, not a wireframe (Chapter 68).

The demonstration: a path, not a tour

Six minutes, and the order matters. Do not open every screen; walk one story through the system, so that the examiner sees the loop close:

StepShowsSeconds
1The owner sets today's stockFR-5, FR-6, and the morning routine of the manual40
2A student signs in and orders two dishes for 12:40FR-2, FR-4, FR-7 to FR-10, and the cut-off60
3The same student asks for three of a dish with two leftthe refusal a student can act on, FR-1130
4The counter works the order: preparing, ready, collected and paidFR-14, FR-15, and the self-refreshing list60
5The kitchen list for that slotFR-1630
6The owner reads the day's reportFR-1740
7The same order on the phone appthe APK of Chapter 5940
8Stop the server and start it: the order is still thereit is a system, not a program30
9Ask /api/health while MySQL is stopped, and again with it runninghow you would know it was down30

Six minutes, three hundred and sixty seconds. Steps 8 and 9 are the ones most teams never think to show, and they are worth more than another screen: they say that the team thought about the system running rather than only about the code compiling.

Then one minute to close: what we learned, in three sentences (Chapter 66's three), and an invitation to questions.

Three habits that make a demonstration look practised:

  • Say what you are about to do before you do it. "I will now ask for three when two are left, and you will see the message a student gets."
  • Narrate the state, not the mouse. "Ganesh at the counter is seeing this list refresh by itself", not "now I click here".
  • Use the seeded names. Priya orders, Ganesh serves, Lata reads the report. It reads as a system in use rather than as a form being filled.

Who speaks when

Four members, and the rule is that each explains what they built, because the viva will ask them anyway (Chapter 76):

PartSpoken byBecause
Problem, scope, requirementsthe member who ran the interviews and wrote the SRSthey can answer "how do you know"
Architecture and the hard partthe member who wrote the stock rulethe design marks are decided here
Testing and verificationthe member who wrote the tests and ran the load testnumbers need their author
Deployment, the app, resultsthe member who deployed itso can answer "where does it run"
munotes.in483

Presentation and Demonstration

One member keeps time and drives the machine, and nobody stands behind the person typing. Agree who answers a question that lands between two people, and agree that whoever is asked answers, rather than the confident one answering everything: an examiner marking four students will notice.

Rehearse it twice, on the machine

The first rehearsal always finds something, and it is never what you expected. The worked team's two rehearsals found: the projector made their contrast look fine but their Only 2 left note unreadable from four metres, so the demonstration zoomed the browser to 125 per cent; and the counter page's ten-second refresh meant a silence they had not planned for, so step 4 now has a sentence to say while it happens.

Rehearse on the machine you will use, from the cold start of Chapter 71, with the phone already prepared, and to the clock. Twelve minutes is shorter than anybody believes: a team that has not timed it reaches slide 6 as the examiner asks them to wrap up, which means the hard part, the testing and the results are never shown.

When something breaks

It will, in a room with one projector and four nervous people. What is assessed is whether you know your own system.

  • Say what should have happened, in the mechanism's own words: "the list refreshes every ten seconds, so it should have moved; let me reload it."
  • One minute, then move on and come back. Chapter 71's rule.
  • Have the rehearsed fallbacks. The three that cover almost everything: the browser at phone width instead of the phone; npm run db:setup to put the data back in seconds; and the printed screenshots, which are in the report you handed them.
  • Never blame the network you removed. You have already made the demonstration local (Chapter 71), so a failure is yours and is best treated as interesting rather than embarrassing.

The guide's internal review

Different event, same preparation. Your guide has seen the increments, so this is about the whole against the plan: what was planned, what was built, what slipped and why, what the review of each increment changed, and what you learned. Bring the plan (Chapter 17) and mark it up honestly: a team that says "the stock rule took two days longer than its six-hour estimate and here is what we did about it" is showing exactly the judgement the five marks are for.

munotes.in484

Presentation and Demonstration

Do this for your project

  1. Prepare two talks: the guide's, about the plan; the examiner's, about the problem.
  2. Ten slides, one idea each, a title that states it, no paragraphs.
  3. Give a slide to the hardest thing you solved, and be able to show its code.
  4. Write the demonstration as one story through the system, not a tour of screens.
  5. Include a restart and a health check: show that it is a system.
  6. Give each member the part they built, and agree who answers what.
  7. Rehearse twice, on the machine, to the clock, from a cold start.
  8. Prepare three fallbacks and rehearse them too.

Mistakes that cost marks

Giving the guide's progress talk to the examiner, who does not know what any of it is.

Thirty slides, and the demonstration cut short.

Reading the slides aloud.

A tour of every screen, with no story and no loop closed.

One member speaking for all four, which the marks are not designed for.

No rehearsal, so nobody knows it takes nineteen minutes.

A wireframe on the "what we built" slide, three months after it was built.

Debugging in front of the examiner for five minutes.

Quick revision

  • Two events: the guide's review (5, internal, progress against the plan) and the examiner's demonstration (10, external, plus the viva's 5).
  • The examiner knows nothing: begin with the problem and a number.
  • Twelve minutes: 5 of slides (300 s), 6 of the system (360 s), 1 to close; 720 in all.
  • Ten slides, one idea each; give one to the hardest part and show its code.
  • The demonstration is one story: stock, order, refusal, counter, kitchen, report, phone, restart, health.
  • Each member speaks for what they built; one keeps time and drives.
  • Rehearse twice on the machine, to the clock, and rehearse the fallbacks.
  • When it breaks: say what should have happened, one minute, move on.

Questions you must be able to answer

1. How do the guide's presentation and the examiner's demonstration differ? In what the audience knows. The guide has followed the project and is assessing progress and judgement against the plan; the examiner has never seen it and must be given the problem, the system, the design and the evidence in about twelve minutes.

2. How should twelve minutes be divided? Roughly five minutes of slides, six of the working system, and one to close and invite questions. The system gets the larger half because ten of the external marks are for the demonstration.

munotes.in485

Presentation and Demonstration

3. What should a presentation devote a whole slide to that most do not? The hardest problem the team solved, and its solution. For the worked project that is the stock rule: two orders for the last plate cannot both succeed, four lines of SQL in one transaction, and a test that proves it.

4. Why demonstrate a restart and a health check? Because they show that the team built a system rather than a program: the data survives the process, and there is a way to tell whether the application and its database are up. Very few student demonstrations show either.

5. Why should each member present the part they built? Because the viva will ask them about it individually, and because four students are being marked. A single presenter leaves the others with nothing an examiner can assess.

6. What do you do when the demonstration fails in front of the examiner? Say what should have happened, in the mechanism's own terms, give it about a minute, use a rehearsed fallback, and return to it later. Knowing why it failed is itself evidence that you built it.

Contents This chapter on its own page

munotes.in486

Chapter Seventy-Five

The External Evaluation: How the Thirty Marks Are Earned

Syllabus topic Module 2's evaluation: "Working Application Demonstration", "Technical Design & Implementation", "Project Report Evaluation", "Viva Voce".

In one line

An examiner marks what is in front of them, so every good thing about your project that cannot be seen in twelve minutes has to be made visible by an artefact you put on the table.

In the wording to use when asked: the external evaluation assesses the demonstrated system, the quality and coherence of its design and implementation, the report as a document, and the candidate's own understanding, out of thirty marks; the assessment is evidential, so each criterion is met by something the examiner can inspect or ask about.

What an examiner can actually see

This is the whole chapter in one table, and it is where most marks are lost:

What students believe is judgedWhat the examiner can see
how many hours we workednothing at all
how difficult it wasonly if you name the hard part and show its solution
how much we learnedonly what you can say in three sentences
how well we worked as a teamthe commit history, the pull requests, and who can answer what
that we tested itthe test report, the counts, and the traceability matrix
that we planned itthe plan, marked up against what happened
that we understood the designwhether the code matches the diagrams

Effort is invisible; artefacts are not. Everything on the right exists already if you have followed this book, which means the work of the last week is not to produce it but to put it where the examiner will look.

Working Application Demonstration, 10 marks

The examiner sees the system run, or does not. What separates the bands, in practice:

BandWhat it looks like
Full marksstarts from cold, one story walked end to end, a refusal shown honestly, a restart, a health check, and the phone
Most of themit all works, but as a tour of screens with no story and no error case
Halfit works after some fiddling, or only the happy path exists
Fewit does not start, or the demonstration is of screenshots

Chapter 71 is the readiness, Chapter 74 is the path. The two things that cost most here are a first start in front of the examiner and never showing a refusal: a system that has only ever been shown doing the right thing invites the question of what it does with the wrong thing, and that question is then answered live.

Technical Design and Implementation, 10 marks

This is the component students prepare least and it is worth as much as the demonstration. The examiner can open your diagrams, your repository and your code side by side, and what they are really asking is one question: does the code match the design, and did somebody decide it?

munotes.in487

The External Evaluation: How the Thirty Marks Are Earned

Five things to put in front of them, all of which you already have:

Put on the tableIt answers
1the architecture diagram, and the code's folders beside itis the design real, or drawn afterwards
2the decision records: the stack, paise, hashed sessions, one clock, the conditional updatedid somebody choose, or did it happen
3the layer check (npm run layers)is the layering enforced or aspirational
4the ER diagram and sql/schema.sql, column for columndo the model and the database agree
5the four lines of SQL that cannot oversell, and the test that proves itcan you explain your own hardest code

Item 2 is the cheapest ten minutes in the whole project. A decision record is five lines: what was decided, why, and what it costs (Chapter 36). An examiner who reads "we used scrypt because Node.js 22, which we support, has no Argon2, and each hash records its settings so we can move later" is reading an engineer, not a student who copied a tutorial.

And be ready for the opposite question, which is the fair one: where does the code not match the design? There is always somewhere. Naming it yourself, with the reason, is worth more than being caught by it.

Project Report Evaluation, 5 marks

Judged as a document, in perhaps ten minutes of reading. What a reader reaches for first, in order: the contents page, the results, the limitations, the traceability matrix, the references. Chapter 73's fourteen checks are what protect these five marks, and the two that matter most are that every number says where it came from and that the limitations section exists.

A report cannot rescue a broken demonstration, and a demonstration cannot rescue a report with invented numbers, because the viva joins them: the examiner reads a number in the report and asks how it was measured.

Viva Voce, 5 marks

Five marks for whether the project is yours. It is not a general knowledge test: the questions come out of your own project, and Chapter 76 collects them with answers. The only preparation that works is to have done the work and to have read your own report.

Making the invisible visible

Invisible virtueThe artefact that proves itChapter
We planned itthe Gantt chart, marked up with what actually happened17
We prioritisedthe MoSCoW table, and the Coulds that were not built13
We decided rather than driftedthe decision records36
We testedthe test report, the counts by level, the traceability matrix55
We found and fixed defectsthe issue, its commit, and the test added with the fix56
We worked as fourgit shortlog -sne HEAD, and reviewed pull requests62, 72
We know what it cannot dothe limitations section66
Somebody can run itthe README, followed on a clean clone69
munotes.in488

The External Evaluation: How the Thirty Marks Are Earned

Print two or three of these as a single page each and keep them in a folder on the table. When an examiner asks "how did you divide the work?", the answer is better with a page in their hand.

The day, in order

WhenWhatNotes
130 minutes earlyarrive, find the room, find a socketthe socket is not a joke
220 minutes beforecold start from Chapter 71, place one test order, reset with npm run db:setupprove it before anyone watches
310 minutes beforephone on the hotspot, browser zoom set, other windows closed, notifications offthe projector changes everything
40hand over the report, say who is whonames and roll numbers, once, clearly
5first 5 minutesthe slides: problem, scope, requirements, architecture, the hard part, testing, resultsChapter 74
6next 6 minutesthe demonstration, one story, ending with the restart and the health checkChapter 74
71 minutewhat we learned, and invite questions
8thenthe questions, each member answering for their own partChapter 76
9afterleave the machine as you found it, take everything, thank the examiner

Two notes on step 2. Reset after your own test order, or the examiner's first look is at your rehearsal's data. And do not switch the laptop off between the test and the demonstration: the whole point of the earlier cold start is that the next one is not the first.

Passing, and what to do about a weak component

Forty per cent is needed in each half, so 12 of these 30 and 8 of the guide's 20, counted separately: strong internal marks do not rescue a weak external appearance, and the reverse is equally true.

If one component is weak and the examination is a week away, spend the week in this order, because this is the order of the marks at risk: make the demonstration certain (10), put the design evidence on the table (10), run Chapter 73's checks on the report (5), read your own report aloud to each other (5, and it is most of the viva preparation there is).

Do this for your project

  1. Accept that effort is invisible, and plan what the examiner will see.
  2. Make the demonstration certain first: it is the largest single number.
  3. Prepare the design evidence as pages on the table, not as things you could find.
  4. Write the decision records if you have not; they are five lines each.
  5. Name your hardest problem, and be able to show and explain its code.
  6. Know where the code does not match the design, and say so first.
  7. Run the report checks, and make sure every number says where it came from.
  8. Rehearse the day in the order above, including the reset after your own test order.
munotes.in489

The External Evaluation: How the Thirty Marks Are Earned

Mistakes that cost marks

Preparing the demonstration and nothing else, and losing the ten design marks by default.

Diagrams drawn after the code, which never match it.

No decision records, so every choice looks accidental.

A number in the report that nobody measured, found by one question.

Arriving five minutes before, with a phone that cannot reach the laptop.

Demonstrating on the data left over from your own rehearsal.

One member answering everything, when four are being marked.

Quick revision

  • The external 30: demonstration 10, technical design and implementation 10, report 5, viva 5; pass at 12.
  • Effort is invisible: an examiner marks artefacts and answers.
  • Demonstration: cold start, one story, a refusal, a restart, a health check, the phone.
  • Design: diagram beside the folders, decision records, the layer check, ER beside the schema, the hardest code.
  • Report: contents, results, limitations, traceability, references; every number sourced.
  • Bring the invisible-virtue pages: plan, priorities, decisions, test report, issues, contributors, limitations, README.
  • The day: 30 minutes early, cold start, reset, report, 5 slides, 6 demonstration, 1 close, questions.

Questions you must be able to answer

1. How are the external thirty marks divided? Working Application Demonstration 10, Technical Design and Implementation 10, Project Report Evaluation 5, Viva Voce 5, and 40 per cent of the thirty, which is 12, is needed to pass the external half on its own.

2. Why is "we worked very hard on this" worth nothing to an examiner? Because effort cannot be inspected. Only the demonstrated system, the code and diagrams, the report and your answers can be, so anything you want credited has to exist as one of those.

3. What is the examiner really asking under "technical design and implementation"? Whether the code matches the design and whether somebody decided it: the diagram against the folders, the decision records behind the choices, the layering actually enforced, the database agreeing with the model, and whether you can explain your own hardest code.

4. Why do decision records matter so much for the marks they cost to write? Because they are the only evidence that a choice was made rather than copied. Five lines on why scrypt rather than Argon2id, or why money is stored in paise, turns a defensible answer into a written one.

munotes.in490

The External Evaluation: How the Thirty Marks Are Earned

5. What should a team do in the last week if one component is weak? Work in the order of the marks at risk: make the demonstration certain, then put the design evidence on the table, then run the report checks, then read the report aloud to each other, which is most of the viva preparation available.

6. What is the one thing to do twenty minutes before the examination? The cold start of Chapter 71, with one test order placed and then npm run db:setup to reset, so that the system has already been proved that morning and the examiner's first look is at clean data.

Contents This chapter on its own page

munotes.in491

Chapter Seventy-Six

The Viva Voce: Questions You Must Be Able to Answer

Syllabus topic Module 2's evaluation: "Viva Voce".

In one line

The viva is five marks for whether the project is yours, asked as questions about the things the examiner has just seen and read, and answered best by having done the work and reread your own report.

In the wording to use when asked: the viva voce is an oral examination in which the candidate is questioned individually on the project's problem, requirements, design, implementation, verification and process, to establish authorship, depth of understanding, and the ability to justify decisions.

How it is conducted, and what it is testing

A few minutes each, usually straight after the demonstration, with the report open. The examiner picks questions from what is in front of them: a diagram, a number in section 6, a line of code that was on the screen a minute ago. Nothing is memorised and nothing is general knowledge.

Three things are being tested, and they are worth separating because students prepare only the first:

The question behind the questionHow it is asked
1Is this yours?"Which part did you write?" and then something specific about that part
2Do you understand it?"Why does this work?" and "what happens if two people do it at once?"
3Can you judge it?"What would you do differently?" and "what does it not do?"

The third is where the marks separate. Two teams with identical systems are not equal if one can say what it would change and why.

Four rules for answering

  • Answer first, then reason. "Because two orders for the last plate could both succeed" and then how. An answer that arrives after thirty seconds of context sounds like a search.
  • Say the number. "Ninety-five per cent within a second, with a hundred students in a minute" beats "it was fast", and the number is in your report.
  • Say "I do not know" when you do not, and then say how you would find out. It costs almost nothing; a confident wrong answer about a standard you never read costs a great deal.
  • Never quote a standard you have not read. "ASVS 5.0 asks for at least eight characters at level 1, and strongly recommends fifteen" is defensible because you read the section; "ISO 42010 requires the following seven views" is not, if you read a catalogue entry.

The problem and the scope

1. Why this problem? Because we could observe it and measure it. Over five days we counted 212 students served and 31.2 leaving the queue without buying each day, and timed waits averaging 16 minutes in a 40-minute break. For your own project, the answer must contain a number you collected yourself.

2. Who are the users? Students, the counter staff, the kitchen and the owner, and each wants a different thing: students want to not queue, the counter wants an ordered list, the kitchen wants a count, the owner wants the takings and the waste.

munotes.in492

The Viva Voce: Questions You Must Be Able to Answer

3. What is out of scope, and why? Online payment, SMS alerts, delivery, penalties for orders not collected, other canteens, and the stock of raw materials. Each was written down with its reason: payment because the canteen takes cash and a gateway needs an account we cannot open; delivery because nobody would carry it.

4. Is this not just a food-ordering app? The problem is one counter and a 40-minute break, not food delivery. What the system removes is the first queue, ordering, and it gives the kitchen a count in advance, which is what the owner asked for.

5. What would the canteen do if it stopped working? Exactly what it does today: take orders at the counter. The system adds a path and removes none, which is also why the owner agreed to a trial.

Requirements

6. How many requirements are there, and how do you know they are complete? Seventeen functional and twelve non-functional. Complete against the stakeholders we interviewed and the use cases we drew: every use case is covered by at least one requirement, and every requirement traces to a test or a screenshot in section 6 of the report.

7. Give an example of a requirement you changed, and why. The counter's list refreshing itself became a Should after the first increment review, because Ganesh said he would not remember to reload it during a rush. It was built first in the second increment.

8. What is a non-functional requirement, and give one of yours with its number? A quality the system must have rather than a thing it must do. NFR-1: with 100 students ordering in the same minute, 95 per cent of menu and order requests are answered within 1 second and none fails.

9. How did you prioritise? MoSCoW, with the effort in hours: Musts 46, Shoulds 18, Coulds 14, 78 in all. The Musts are 59 per cent of the effort, and the Coulds were the contingency. All the Musts and all the Shoulds were built; none of the Coulds.

10. What is an assumption, and what did one cost you? Something taken to be true without proof. A-2 is that the owner sets the day's stock before 11:00; if she does not, yesterday's numbers are offered, because the stock is one number per dish and nothing resets it overnight. We guarded it with the manual's morning routine and the counter's 11:00 check, and recorded the limitation and its proper fix.

munotes.in493

The Viva Voce: Questions You Must Be Able to Answer

Design and UML

11. Which UML diagrams did you draw, and why those? Use case, class, sequence, activity, ER and deployment: the six the syllabus names. Each answers a different question, who uses it, what things exist, what happens in order, how a process flows, how the data relates, and where it runs.

12. Show me your class diagram and explain one association. Five classes and twenty-two attributes. Order to OrderItem is a composition: an order item has no life without its order, and deleting an order deletes its items, which the schema enforces with a cascade.

13. Why does your sequence diagram have a loop around the stock? Because an order has several items and each one's stock is taken inside the same transaction; if any item is short, the whole transaction is rolled back and nothing is taken.

14. What does your activity diagram show that the sequence diagram does not? The decision points as a process: the cut-off check, the one-order-per-slot check, and the refusal path back to the student's own choice, without reference to which object does what.

15. Does your ER diagram match your database? Column for column: five tables, thirty columns, the same names, types, keys and NOT NULLs. We check it rather than trust it.

16. Where does the design not match the code? In one name. The class diagram calls the order's pickup time slot; the column is pickup_slot, because slot alone reads badly in SQL beside pickup_date. Everything else agrees column for column, which we check rather than claim, and the daily report has no class of its own because nothing needed to hold its state: it is a query in the orders service.

Architecture and decisions

17. Describe your architecture in three sentences. Two clients, browser pages and an Android app that shows those pages, talk HTTP to one server application. The server is in layers: routes, then services and validation, then rules, then a store that alone speaks SQL. MySQL holds the data, and the application reaches it on the loopback address.

18. Why this stack? Decision record 1: JavaScript on both sides so that four students share one language, Express because it is small enough to read, MySQL because the college lab has it and the data is relational. The alternatives we considered, PHP, Django and MERN, are in the same record with why each was not chosen.

19. Why is money stored in paise? Decision record 2. Rupees as a floating-point number cannot represent 0.1 exactly, and totals drift; an integer number of paise is exact, and the page divides by 100 to show it.

20. What is a decision record, and how many do you have? Five lines: what was decided, why, and what it costs. We have five: the stack, paise, hashed sessions, one clock, and the conditional stock update.

munotes.in494

The Viva Voce: Questions You Must Be Able to Answer

21. Why does the server work out every time itself? Decision record 4: one clock. The server may be in another time zone, and the database only stores what it is given, so every time is computed on the canteen's own clock with Intl.DateTimeFormat in the application's zone. DEMO_TIME can stop that clock for a demonstration, and the server says so at start-up.

The database

22. Why five tables? Users, menu items, orders, order items and sessions. Each holds one kind of thing, and nothing repeats: an order names its user by id and its items name their menu item by id.

23. What is normalisation, and where did you stop? Removing repetition so that one fact lives in one place. Third normal form, with one deliberate exception: an order item stores the price at the time of ordering, because a price that changes tomorrow must not change yesterday's bill.

24. Why is the price stored on the order item as well as on the menu item? That exception exactly: the menu item's price is today's, the order item's is the price that was charged. Without it, last week's report changes when the owner raises a price.

25. How do you stop two students buying the last plate? One conditional UPDATE inside a transaction: SET stock_left = stock_left - ? WHERE id = ? AND is_available = TRUE AND stock_left >= ?. The database decides, one statement at a time, and if it changed no rows the order is refused with a message naming what is left.

26. What would happen with a read and then a write instead? Both requests would read two left, both would write one, and two orders would exist for one plate. We wrote a script that demonstrates it both ways on a real database, because it is easier to believe when you have seen it.

The code

27. Which part did you write? Answer for yourself, honestly, and be specific: the file, what it does, and one thing in it you would explain to a new developer.

28. Walk me through what happens when a student taps Place order. The page posts the cart and the slot; the route validates the body; the service checks the cut-off on the canteen's clock, checks the student has no order for that slot, opens a transaction, takes each item's stock conditionally, writes the order and its items, commits, and returns the order number. Any refusal ends the transaction with nothing taken.

29. Why are your rules in a module of their own? Because they are pure functions with no database, no network and no clock of their own, which makes every one of them testable by giving it what it needs. The comment at the head of the file says so, to stop the next person adding a query.

munotes.in495

The Viva Voce: Questions You Must Be Able to Answer

30. What is validation, and where does it happen? Checking every input against what it must be, on the server, for every request, whatever the page already checked. The browser's checks are a courtesy to the user; the server's are the ones that hold.

31. Show me your error handling. One handler at the end of the middleware: known refusals become their own status and a message a user can act on, and anything unexpected becomes 500 with a line in the log and nothing leaked to the browser.

32. What does the Android app contain? One screen of Kotlin holding a WebView that shows the same pages, with the server's address fixed at build time. The rules stay on the server, so a change to the pages needs no new app, and that is why it is one file.

Security

33. How are passwords stored? As scrypt hashes at N = 2^14, r = 8, p = 5, one of the settings the OWASP Password Storage Cheat Sheet lists, with a random salt each, compared in constant time. The hash records its own settings, so a later move to Argon2id can re-hash at each student's next sign-in.

34. Why not Argon2id, which OWASP prefers? Because Node.js added Argon2 to its own crypto module only in version 24.7.0, and NFR-12 supports Node.js 22, where it does not exist. We confirmed it by asking each installed version.

35. What is in your session cookie? Thirty-two random bytes, and nothing else. The database stores the SHA-256 of that value with its expiry, so a stolen database gives nobody a usable session, and a session lasts eight hours.

36. How do you stop somebody guessing a password? Five failed sign-ins for one address and email pair earn a refusal with a Retry-After of about fifteen minutes. Registration also refuses passwords on a published list of common ones.

37. How do you stop SQL injection? Every value goes to MySQL as a parameter, never as text in a statement; the store is the only layer that speaks SQL, and a test sends '; DROP TABLE orders; -- as a dish name and then reads it back unchanged.

Testing

38. What did you test, and how much? One hundred and seventeen automated tests: 43 unit, 29 black box, 45 integration, on one command. Then twenty-six written test cases by hand, an acceptance session with the owner and the counter staff, a load test, an input sweep and a security check against each control.

munotes.in496

The Viva Voce: Questions You Must Be Able to Answer

39. What is the difference between your unit and integration tests? The unit tests give a function what it needs and need no database. The integration tests build the real application against a test database that is rebuilt from the schema, and go in through HTTP.

40. Which test found a real defect? Not an automated one. A written test case, run by hand in a browser, showed the sold-out message as "Only 2 Chicken Biryani left. 3 2", because nothing until then had looked at a sentence a student reads. It was raised as an issue, fixed, and a test was added with the fix.

41. What is a traceability matrix? A table with a row for each requirement and a column saying which test covers it. Ours exposed two endpoints with no test of their own, which is what it is for.

42. What did the load test show? That NFR-1 holds: with the lunch rush simulated, every menu and order request was answered within a second and none failed, and the report says on what machine, with what data and doing what, because a percentage without those cannot be checked.

43. What is not tested? The Android app has no automated test, and the deployment is checked by hand. Both are in the report.

Deployment and operations

44. Where does it run? On the college lab's desktop, Ubuntu 24.04: Nginx on port 80, the application on 127.0.0.1:3000 under systemd, MySQL on 127.0.0.1:3306. Nothing else is open to the network.

45. Why is it not on HTTPS? Because the lab machine has no public name or address, so no certificate can be issued for it. It is a recorded limitation, and its fix is a public server with a name, which also removes the Wi-Fi-only limit.

46. What happens if the application crashes? systemd starts it again within about three seconds, which NFR-10 asks for within ten, and we proved it by killing the service and reading the journal. Nothing in flight is lost, because an order is committed before its number is sent.

47. How would you know it was down? /api/health answers whether the application is up and whether it can reach its database. Nobody is alerted automatically, and that is a recorded limitation.

48. What is TRUST_PROXY for? Behind Nginx, every request otherwise appears to come from Nginx, and the limit on failed sign-ins is keyed on the address, so one student's five wrong passwords would lock out everybody. Set to loopback, the real address is read from the header Nginx sets.

The process and the team

49. How did you divide the work? By area, with each member owning one: requirements and the report, the backend and the database, the frontend, and testing and deployment. git shortlog shows it, and every pull request was read by somebody who did not write it.

munotes.in497

The Viva Voce: Questions You Must Be Able to Answer

50. What process did you follow? A design phase to a fixed date, because the Module 1 documents are due then, and two two-week increments after it, each ending in a working version shown to the owner and the guide. It is in the report with why it was chosen over waterfall and over Scrum.

51. Did the plan hold? Mostly. Sixty-five working days on the critical path with nine of slack, and the stock rule took two days longer than its six-hour estimate, which is what the Coulds were for: none of them was built, and everything a user needs was.

52. What did the increment reviews change? The counter's self-refreshing list moved to the front of the second increment, and after acceptance the order number on the counter's list was made larger, because Ganesh was reading it twice.

The four questions that separate teams

53. What would you do differently? Store a date with each day's stock from the beginning, and put the system on a public server with a name, so that HTTPS and off-campus use came for free. Both are in the future work with the reason they were not done.

54. What does your system not do? Eleven recorded limitations, and the honest ones first: it works only on the college Wi-Fi over plain HTTP; nobody is alerted when sign-ins fail in bursts; the counter cannot see the stock numbers; an account can be switched off only in the database; and the kitchen list is on a screen rather than on paper, which the head cook asked for.

55. What did you copy, and from where? The common-password list, which is a published list under its own licence, recorded in data/README.md with the commands that made our copy. Everything else was written by us, and the libraries are two, named in the lock file.

56. Which part are you least sure of? Answer it. The willingness to name it is the whole of the third mark. For the worked project: the Android app, because it has no automated test and was built last, and because a WebView hides the differences between phones that a native app would make us face.

Do this for your project

  1. Reread your own report the night before, especially section 6 and the limitations.
  2. For every number in it, know how it was measured.
  3. Know which parts you wrote, and one thing in each you could explain to a stranger.
  4. Prepare the four closing questions properly: they are what separate two identical systems.
  5. Practise answering first and reasoning second.
  6. Agree who answers what, and let whoever is asked answer.
  7. Say "I do not know" when you do not, and say how you would find out.
  8. Never quote a standard you have not read; say what you did read.
munotes.in498

The Viva Voce: Questions You Must Be Able to Answer

Mistakes that cost marks

Answering a question about somebody else's part because they hesitated.

"It was fast" and "it is secure", when your own report has the numbers.

Quoting a standard's contents from memory.

Not knowing what your system cannot do, after writing a limitations section.

Reciting the report instead of answering the question asked.

Guessing. An examiner asks the next question from your answer, and a guess leads somewhere you cannot follow.

Quick revision

  • Five marks for whether the project is yours, asked from what the examiner has just seen and read.
  • Three questions behind the questions: is it yours, do you understand it, can you judge it; the third separates teams.
  • Answer first, reason second; say the number; say "I do not know" and how you would find out; never quote an unread standard.
  • Know your own numbers: the observation week, 17 and 12 requirements, 78 planned hours, 117 tests, 5 tables and 30 columns, 6 statuses and 5 moves.
  • Know the hard part and its four lines of SQL, and why a read-then-write fails.
  • Know the eleven limitations, and the two things you would do differently.
  • Each member answers for their own part.

Questions you must be able to answer

That has been this chapter, and this book. The last four are the ones to prepare last and answer first: what would you do differently, what does it not do, what did you copy, and which part are you least sure of. A team that answers those four well has already shown the examiner everything the other fifty-two were looking for.

Contents This chapter on its own page

munotes.in499

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Issue
Done!