Assignment 4: Full-Stack Next.js Application
Release Date: March 6, 2026
Due Date: March 22, 2026, 11:59 PM EST
Weight: 12.5% of final grade
Overview
Building on Assignment 3, you will convert the Paper Management System into a full-stack Next.js App Router application using:
- Server Components
- Server Actions
- Prisma ORM
- TypeScript
- Tailwind CSS
- shadcn/ui
You will:
- Display papers from PostgreSQL
- Create papers with existing authors on a dedicated page using Server Actions
- Create authors on a dedicated page using Server Actions
- Use TypeScript with Prisma-generated types for type safety
- Apply clean and consistent styling with Tailwind CSS and shadcn/ui
Learning Objectives
After completing this assignment, you will be able to:
- Build a full-stack Next.js application with TypeScript
- Use Server Components to fetch and display typed data
- Implement Server Actions for typed data mutations
- Integrate Prisma ORM in a Next.js app
- Apply consistent Tailwind CSS styling
- Configure and use shadcn/ui components
Requirements
Before you start coding
After reading the assignment requirements below, pause and think about what you expect will be the hardest part of this assignment. This will help you answer Q1 in reasoning.md, which you will submit together with your code.
See the AI Usage Policy for details.
The detailed requirements can be found in the next Detailed Requirements section.
Starter Code
Download the starter code archive:
curl -o starter-code.tar.gz https://www.eecg.utoronto.ca/~cying/courses/ece1724-web/assignments/assignment-4/starter-code.tar.gzAlternatively, download the archive here.
The archive includes:
assignment-4/
├── app/
│ ├── papers/
│ │ └── create/
│ │ └── page.tsx # Create paper page
│ ├── authors/
│ │ └── create/
│ │ └── page.tsx # Create author page
│ ├── api/
│ │ └── papers/
│ │ └── route.ts # API endpoint
│ ├── favicon.ico # Default favicon (auto-served by Next.js)
│ ├── globals.css # Tailwind setup
│ ├── layout.tsx # Root layout (DO NOT MODIFY)
│ └── page.tsx # Home page
├── components/
│ ├── PaperCard.tsx # Paper display
│ ├── PaperForm.tsx # Paper form for creating new paper
│ ├── PaperList.tsx # Paper list
│ └── CreatePaperForm.tsx # Client-side wrapper for PaperForm
├── lib/
│ ├── actions.ts # Server Actions
│ ├── prisma.ts # Prisma Client (DO NOT MODIFY)
│ └── ui.ts # Shared Tailwind class strings (DO NOT MODIFY)
├── prisma/
│ └── schema.prisma # From Assignment 2
├── eslint.config.mjs # ESLint config (DO NOT MODIFY)
├── next-env.d.ts # TypeScript declarations for Next.js (DO NOT MODIFY)
├── next.config.ts # Next.js config (DO NOT MODIFY)
├── package.json # Dependencies and scripts
├── postcss.config.mjs # PostCSS config (DO NOT MODIFY)
├── prisma.config.ts # Prisma ORM config (DO NOT MODIFY)
├── tsconfig.json # TypeScript config (DO NOT MODIFY)
└── tests/
├── reset.sql # SQL script used to reset database tables
└── sample.test.ts # Sample Playwright test cases- Next.js + TypeScript + Tailwind setup
- Prisma configuration
- Component skeletons with TODO comments
- Sample Playwright tests
Do not modify:
app/layout.tsxapp/favicon.ico: Default favicon (auto-served by Next.js)lib/prisma.tsandprisma.config.ts- Next.js/TypeScript configuration files (e.g.,
next*.ts,eslint.config.js,tsconfig.json) lib/ui.ts: Shared Tailwind class strings to keep styling consistent across components
shadcn/ui Setup Required
The starter code does not include files generated by shadcn/ui.
You must:
- Initialize shadcn/ui
- Choose
Neutralas the base color
- Choose
- Add the
Buttoncomponent- This is only component you need from shadcn/ui for this assignment
This will update app/globals.css and generate lib/utils.ts, components.json, and components/ui/button.tsx. These files are not provided. You must generate them yourself.
Paper Management
- Display Papers: Show all papers from the database on the home page
- Create Papers: Add new papers with existing authors via a form on
/papers/create - Create Authors: Add new authors via a form on
/authors/create
Next.js Implementation
- Provide an API route (
/api/papers) to fetch papers - Use Server Components for fetching and rendering papers for the home page
- Use Server Actions for creating papers and authors with navigation to
/ - Use Prisma ORM with TypeScript types to interact with the database
- Implement Suspense for loading states
- Use Tailwind CSS and shadcn/ui
Buttoncomponent
Detailed Requirements
Home Route ("/") - Server Component
Page Layout

In app/page.tsx
- Main heading:
Paper Management System(text-3xl font-bold) - Two buttons (shadcn/ui
Button):Create New Paper(/papers/create)Create New Author(/authors/create)
- Papers section:
- Subheading:
Papers(text-xl font-semibold)
- Subheading:
Data Fetching
In app/page.tsx
-
Fetch papers from
/api/papers -
Return an object with:
papers: Array of fetched papers on successerror:nullon success, orError loading papersif fetch fails
-
If error occurs, display
Error loading paperswith<p data-testid="papers-error" className="text-sm text-muted-foreground"></p>
-
Otherwise, use
PaperListto render papers, wrapped in<Suspense>with a fallback of<p className="text-sm">Loading papers...</p>
Paper List Display
In components/PaperList.tsx
-
Display each paper using
components/PaperCard.tsx:- Title
<h3 className="text-lg font-semibold leading-snug"></h3> - Publication venue
<p className="text-sm text-muted-foreground"></p> - Year
<p className="text-sm text-muted-foreground"></p> - Author names (comma-separated)
- Title
-
If there is no paper in the database, show
<p className="text-sm text-muted-foreground">No papers found</p>
Create Paper Route (/papers/create)
Page Layout

- Heading:
Create New Paper(text-2xl font-bold) - A
Backbutton (shadcn/uiButton) at the top-right that links to the home page (/) - Fetch authors server-side in
app/page.tsxwith ascendingidorder - Pass authors to
CreatePaperForm - Use
CreatePaperForm(a Client Component) to render:-
PaperFormwith:-
Title:
<input type="text" name="title" id="title" className={inputClass} /> -
Published In: Style with
className={inputClass} -
Year: Style with
className={inputClass} -
Authors:
<select multiple name="authorIds" id="authorIds" className={selectClass} data-testid="author-dropdown" >- Show
No authors availablein dropdown if author list is empty
- Show
-
-
Submit button:
<Button data-testid="create-paper-btn" type="submit"> Create Paper </Button>
-
Server Action createPaper
In lib/actions.ts
-
Validation: On submit, validate in this order and display only the first error encountered:
Title is requiredif empty string or string containing only whitespacePublication venue is requiredif empty string or string containing only whitespacePublication year is requiredif emptyValid year after 1900 is requiredif not an integer greater than 1900Please select at least one authorif no authors selected
-
Success: Creates the paper in the database and returns (no redirect here)
Client-Side Logic
In components/CreatePaperForm.tsx
-
On success:
- Show
Paper created successfully(data-testid="status-message","text-sm") - Wait 3 seconds
- Redirect to
/
- Show
-
On error: Show the error message from the Server Action (
data-testid="status-message","text-sm text-red-800")
Create Author Route (/authors/create)
Page Layout

- Heading:
Create New Author(text-2xl font-bold) - A
Backbutton (shadcn/uiButton) at the top-right that links to the home page (/) - Form with:
- Name:
<input type="text" name="name" id="name" className={inputClass} /> - Email (optional): Style with
className={inputClass} - Affiliation (optional): Style with
className={inputClass}
- Name:
- Submit button:
Create Author- shadcn/ui
Buttonwithdata-testid="create-author-btn"
- shadcn/ui
Server Action createAuthor
In lib/actions.ts
- Validation:
Name is requiredif empty string or string containing only whitespace
- Success: Creates the author in the database and returns (no redirect here)
Client-Side Logic
In app/authors/create/page.tsx
-
On success:
- Show
Author created successfully(data-testid="status-message","text-sm") - Wait 3 seconds
- Redirect to
/
- Show
-
On error: Show
Name is requiredorError creating author(data-testid="status-message","text-sm text-red-800")
API Route (/api/papers)
Implement app/api/papers/route.ts
- Use Prisma to retrieve all papers in the database with ascending
idorder - Include the
authorsrelation in the query to fetch each paper’s associated authors - Return the results as a JSON response using
NextResponse.json()-
Note: In Assignment 4, the
/api/papersroute should return a JSON array of papers.This differs slightly from Assignment 2, where the API response wrapped the data in an object
{ papers, total, limit, offset }. Since Assignment 4 is a Next.js full-stack application, the API response format is simplified for this assignment.
-
Implementation Order (Recommended)
- Configure Prisma + migration
- Implement API route
- Implement Home page
- Implement
PaperCardandPaperList - Implement create paper flow
- In
app/papers/create/page.tsx, fetch authors server-side and pass them toCreatePaperForm - Implement
components/CreatePaperForm.tsxto handle form submission - Implement
components/PaperForm.tsxto render the form UI and the submit button, including client-side logic for success/error messages and a 3-second redirect - Add the
createPaperServer Action insrc/lib/actions.tswith validation and paper creation logic
- In
- Implement create author flow
- Update
app/authors/create/page.tsxto render the form UI and handle submission with client-side logic for success/error messages and a 3-second redirect - Add the
createAuthorServer Action insrc/lib/actions.tswith validation and author creation logic
- Update
- Test all flows carefully
Getting Started
-
Download the starter code archive.
-
After extracting the archive, install dependencies:
npm install -
Create
.envwith your PostgreSQL credentials.envDATABASE_URL="postgresql://your-os-username@localhost:5432/paper_management?schema=public" NEXT_PUBLIC_API_BASE_URL="http://localhost:3000"- Replace
your-os-usernamewith your actual OS username - This assignment reuse the database
paper_managementfrom Assignment 2
- Replace
-
Run Prisma migration
npx prisma migrate dev --name init -
Generate the Prisma Client
npx prisma generate -
Start the development server
npm run dev
Testing Your Implementation
A Playwright-based test suite /tests/sample.test.ts is provided in the starter code to help you verify your implementation.
This sample test suite focuses on core functionality and correspond to approximately 70% of the autograder score. The autograder will run similar but more comprehensive tests that check all explicitly stated requirements in this handout, and it will not test anything beyond what is specified.
-
Start Your Server
In one terminal, from
assignment-4/:npm run devMake sure the development server is running before executing the tests.
-
Run the Tests
In another terminal, from
assignment-4/:-
Run the sample test suite:
npx playwright test tests/sample.test.ts
-
Submission Instructions
Please follow the steps below to submit a .tar.gz archive to Quercus.
Prepare reasoning.md
In addition to the required code files, you must submit a short reflection file named reasoning.md.
- Create a folder called
assignment-4. - Inside the
assignment-4folder, create a file calledreasoning.md. - Answer the three required questions described in the AI Usage Policy page.
Important Notes
- Keep your responses brief and specific, with a total length of no more than 300 words.
- This file is intended to capture your genuine thinking process. It is not a long writing assignment required polishing.
reasoning.mdis worth 5% of this assignment grade. Its grading rubric is on the AI Usage Policy page.
Generate the Archive
Make your assignment-4 directory have exactly the following structure:
- route.ts
- page.tsx
- page.tsx
- favicon.ico
- globals.css
- layout.tsx
- page.tsx
- button.tsx
- CreatePaperForm.tsx
- PaperCard.tsx
- PaperForm.tsx
- PaperList.tsx
- actions.ts
- prisma.ts
- ui.ts
- utils.ts
- schema.prisma
- components.json
- eslint.config.mjs
- next-env.d.ts
- next.config.ts
- package.json
- postcss.config.mjs
- prisma.config.ts
- tsconfig.json
- reasoning.md
From the parent directory that contains the assignment-4 folder, create the submission archive:
tar zcvf 1234567890-1724-a4.tar.gz --exclude='.DS_Store' assignment-4- Replace
1234567890with your student number. - The archive must contain exactly one top-level folder named
assignment-4with the required files inside. - Do not include
node_modules/,.next/or any build directories.
Any submission that fails to meet the specified format or structure and requires TA intervention for the autograder to work will receive a 30-point deduction.
Verify Your Submission
To avoid a 30-point deduction, use the provided Python script to verify your submission.
-
Download
verify_a4_submission.pyto the same directory as your.tar.gzfile:curl -o verify_a4_submission.py https://www.eecg.utoronto.ca/~cying/courses/ece1724-web/assignments/assignment-4/verify_a4_submission.pyAlternatively, download the script here.
-
Ensure you have Python installed. If not, install it from python.org .
-
From the same directory as your
1234567890-1724-a4.tar.gz, run:python verify_a4_submission.py 1234567890-1724-a4.tar.gz- Replace
1234567890with your student number.
- Replace
Submit to Quercus
Submit your .tar.gz file to Quercus .
You are allowed to submit unlimited times. Only your latest submission before the deadline will be graded. Quercus automatically appends a suffix to the file name after the first submission — this will not affect grading.
Grading Scheme (100 Points)
The total 100 points correspond to 95% of this assignment’s grade. The remaining 5% comes from your reasoning.md submission.
Grades were released on March 24. If you would like to request a remark, please do so by March 27 (last lecture), following the instructions in the syllabus.
Home Route (/): Paper List Display (34 Points)
-
Page Layout and Links (12 points)
- Rendering (4 points): Displays
Paper Management Systemheading (1 point),Paperssubheading (1 point),Create New Paperbutton (1 point) andCreate New Authorbutton (1 point) - Navigation (4 points): Buttons navigate to
/papers/create(2 points) and/authors/create(2 points) - Styling with Tailwind and shadcn/ui (4 points)
- Main heading uses
text-3xl font-bold(1 point) - Subheading uses
text-xl font-semibold(1 point) Create New Paperuse shadcn/ui<Button>component (1 point)Create New Authoruse shadcn/ui<Button>component (1 point)
- Main heading uses
- Rendering (4 points): Displays
-
Paper List Display via Server Component (22 points)
- Loading State with Suspense (3 points): Shows
Loading papers...during fetch - Error State (2 points): Shows
Error loading papers(data-testid="papers-error") if fetch from/api/papersfails - Empty State (2 points): Shows
No papers foundwhen database is empty. - Paper Rendering (15 points): Add two papers to the database (first paper has two author; second paper has one author)
- First paper displays correct title (2 points), venue (2 points), year (2 points), and comma-separated authors (2 points)
- Second paper displays correct title (2 points), venue (2 points), year (2 points), and author (1 point)
- Loading State with Suspense (3 points): Shows
Create Paper Route (/papers/create) (42 Points)
-
Page Rendering (13 points)
- Layout (6 points): Displays
Create New Paperheading (1 point),Backbutton (1 point), form with title (1 point), publishedIn (1 point), and year (1 point) inputs, andCreate Paperbutton (1 point) Create Paperuse shadcn/ui<Button>component (1 point)- Author Dropdown (6 points): Renders multi-select dropdown (
data-testid="author-dropdown") with all authors from DB (3 points, 3 authors, 1 point each) and showsNo authors availableif empty (2 points) Backbutton navigates to homapage (1 point)
- Layout (6 points): Displays
-
Paper Creation with Server Action (29 points)
- Validation (15 points): Throws correct first-error messages from
createPaperaction:Title is requiredif empty string""(2 points)- Message uses
"text-sm text-red-800"(1 point) Title is requiredif string containing only whitespace" "(2 point)Publication venue is requiredif empty string""(2 points)Publication venue is requiredif string containing only whitespace" "(2 points)Publication year is requiredif empty string""(2 points)Valid year after 1900 is requiredif1900(2 points)Please select at least one authorif none selected (2 points)
- Successful Creation (14 points): Creates paper in DB, shows success message, redirects after 3s
- Shows
Paper created successfully(data-testid="status-message") (2 points) Paper created successfullyuses"text-sm"(1 point)- Cretes a paper with one author: Paper in DB with correct title (1 point), publishedIn (1 point), year (1 point), and author (1 point)
- Redirects to
/after 3s (2 points) - Cretes a paper with two authors: Paper in DB with correct title (1 point), publishedIn (1 point), year (1 point), and authors (2 points)
- Shows
- Validation (15 points): Throws correct first-error messages from
Create Author Route (/authors/create) (20 Points)
-
Page Rendering (8 points)
- Displays
Create New Authorheading (1 point),Backbutton (1 point), form with name (1 point), email (1 point), and affiliation (1 point) inputs, andCreate Authorbutton (1 point) Create New Authorheading uses"text-2xl font-bold"(1 point)Buttonbutton navigates to homapage (1 point)
- Displays
-
Author Creation with Server Action (12 points)
- Validation (4 points):
Name is requiredif empty string""(2 points)- Message uses
"text-sm text-red-800"(1 point) Name is requiredif string containing only whitespace" "(2 point)
- Successful Creation (8 points): Creates one author in DB, shows success message, redirects after 3s
- Shows
Author created successfully(data-testid="status-message") (2 points) Author created successfullyuses"text-sm"(1 point)- Author in DB with correct name (1 point), email (1 point), and affiliation (1 point)
- Redirects to
/after 3s (2 points)
- Shows
- Validation (4 points):
API Route (4 Points)
- API Route (
/api/papers) (4 points)- Returns all papers with authors in ascending
idorder (2 points) - Includes
authorsrelation in response (2 points)- Test: Insert 2 papers with authors, fetch
/api/papers, verify JSON response matches DB withauthorsarray per paper
- Test: Insert 2 papers with authors, fetch
- Returns all papers with authors in ascending
Resources
-
Course Materials:
Questions?
-
Discussion Board:
- Post questions on course discussion board
- Search existing discussions first
- Use clear titles and provide relevant code snippets
-
Office Hours:
- Time: Wednesdays, 4:00 PM — 5:00 PM
- Location: Room 7206, Bahen Centre for Information Technology
-
Tips for Getting Help:
- Start early to allow time for questions
- Be specific about your problem
- Share what you’ve tried
- Include relevant error messages