Color Mode

Capstone Refactoring: Professional Code Organization

In this assignment, you will refactor your application to follow professional organizational patterns used in production applications. You will reorganize validation middleware to maintain strict separation of concerns, and enhance your faculty pages by adding profile images.

This assignment challenges you to apply everything you have learned about MVC architecture, middleware patterns, and code organization. You will work more independently than in previous assignments, making decisions about implementation details while following architectural guidelines. Think of this as preparing your application for growth and demonstrating your understanding of professional development practices.

Part 1: Refactoring Validation Middleware

In your previous assignments (contact form, registration, and login), you placed validation middleware directly in controller files alongside route handlers. This approach kept related code together and simplified learning the concepts. For small applications with just a few forms, this organization works perfectly well and remains a valid choice in professional development.

However, as applications grow and teams expand, maintaining strict separation of concerns becomes increasingly valuable. Middleware serves a distinct architectural purpose separate from business logic, and organizing it accordingly makes codebases easier to navigate, test, and maintain. This refactoring teaches you to recognize when architectural patterns should evolve to support application growth.

Understanding the Trade-offs

Before refactoring, you should understand why both organizational approaches exist in professional codebases and when each makes sense.

Current Approach: Validation with Controllers

Your current structure keeps validation rules in controller files:


            src/
            └── controllers/
                └── forms/
                    ├── contact.js      # route handlers + validation
                    ├── registration.js # route handlers + validation
                    └── login.js        # route handlers + validation
        

Advantages: Everything related to a single feature lives together. When working on the contact form, you have handlers and validation in one file. This reduces context switching and makes the codebase feel cohesive. For applications with fewer than 10-15 forms, this approach remains practical and maintainable.

Disadvantages: Controller files grow larger as validation becomes more complex. The architectural boundary between middleware and controllers blurs, making it harder to understand each layer's responsibilities. Testing becomes more complicated because validation logic is mixed with business logic.

Refactored Approach: Dedicated Validation Middleware

The refactored structure separates validation into its own middleware directory:


            src/
            ├── controllers/
            │   └── forms/
            │       ├── contact.js      # only route handlers
            │       ├── registration.js # only route handlers
            │       └── login.js        # only route handlers
            └── middleware/
                ├── auth.js
                └── validation/
                    └── forms.js        # all form validation
        

Advantages: Clear architectural boundaries make the codebase easier to understand. Middleware responsibilities are explicit and separated from business logic. Testing becomes simpler because each layer can be tested independently. As the application grows, this organization scales naturally. New team members can locate validation logic predictably.

Disadvantages: More files and directories to navigate. Related code is separated across different locations. For very small applications, this structure might feel like unnecessary overhead.

Scaling Validation Organization

In larger applications with dozens of forms, you would create separate validation files for each form: contact.js, registration.js, login.js, and so on. Since your application currently has only a few forms, you will consolidate all validation rules into a single forms.js file. This balances architectural clarity with practical simplicity.

When to Use Each Approach

Choose validation with controllers when building prototypes, small internal tools, or applications with fewer than 10 forms. The simplicity and cohesion outweigh architectural concerns.

Choose dedicated validation middleware for applications expected to grow, codebases with multiple developers, or when strict architectural patterns improve maintainability. The organizational overhead pays dividends as complexity increases.

Your university information system has grown to include multiple forms and will continue expanding. This makes it an ideal candidate for refactoring to the dedicated middleware approach. You are making this change not because the current approach is wrong, but because you are preparing the codebase for future growth.

Understanding Validation Middleware Structure

Before you begin refactoring, you need to understand what validation middleware actually is and how it works with Express.

Express middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application's request-response cycle (next). When you use express-validator, you create validation middleware by calling functions like body() that return middleware functions.

In your controller files, you have arrays of validation rules like this:


            const contactValidation = [
                body('subject')
                    .trim()
                    .isLength({ min: 5 })
                    .withMessage('Subject must be at least 5 characters long'),
                body('message')
                    .trim()
                    .isLength({ min: 10 })
                    .withMessage('Message must be at least 10 characters long')
            ];
        

Each call to body() returns a middleware function. The array itself is also middleware that Express can use. When you pass this array to a route in routes.js, Express runs each validation function in order before calling your route handler.

Your refactoring task is to move these validation arrays out of the controller files and into a dedicated middleware file, then update your imports so the routes can still access them.

Refactoring Instructions

1. Create the Validation Middleware Directory and File

Create the directory structure src/middleware/validation/ and create a new file named forms.js inside it. This file will contain all validation middleware for your form-based routes.

At the top of forms.js, import the body function from express-validator:


            import { body } from 'express-validator';
        

2. Move Validation Rules to the New File

Open each of your three controller files: contact.js, registration.js, and login.js. Each file contains one or more validation arrays (for example, contactValidation, registrationValidation, loginValidation, and updateAccountValidation).

Copy all of these validation arrays from the controller files into your new forms.js file. Make sure to preserve the exact variable names because your routes file already imports these specific names.

Pay careful attention to validation arrays that use custom validation functions. For example, the registration validation includes custom validators that compare confirmEmail with email and confirmPassword with password. These use req.body to access other form fields, so make sure you copy the complete validation logic including all .custom() methods.

3. Export the Validation Rules

At the bottom of forms.js, export all four validation arrays as named exports:


            export { 
                contactValidation, 
                registrationValidation, 
                loginValidation,
                updateAccountValidation
            };
        

Using named exports allows you to import specific validation rules by name in your routes file, making the imports clear and explicit.

4. Clean Up Controller Files

Now that validation rules live in forms.js, you need to remove them from the controller files.

In each controller file (contact.js, registration.js, and login.js):

  • Remove the import statement that brings in body from express-validator (you no longer need it in the controller)
  • Remove all validation array definitions (like contactValidation, registrationValidation, etc.)
  • Remove the validation array names from the export statement at the bottom of the file

After cleanup, each controller file should only import validationResult from express-validator (because the route handlers still use it to check for validation errors), and should only export the route handler functions.

For example, contact.js should import validationResult at the top and export only showContactForm, processContactForm, and showContactResponses at the bottom. The contactValidation array should be completely gone from this file.

5. Update Route Imports

Open src/controllers/routes.js. This file currently imports validation rules and route handlers together from each controller. You need to split these imports so validation rules come from the new middleware file and route handlers continue coming from controllers:

  1. Remove validation imports from controller import statements.
  2. Add a new import statement to bring in all the validation rules from forms.js.

Pay close attention to relative paths. Controller imports use ./forms/ because routes.js and the forms directory are both in the controllers directory. Middleware imports use ../middleware/ because you need to go up one level from controllers to reach middleware.

6. Test the Refactoring

Restart your server and systematically test each form to verify the refactoring worked correctly:

  • Visit http://127.0.0.1:3000/contact and submit the form with valid data. Verify it saves correctly and shows a success message.
  • Submit the contact form with invalid data (empty subject, message too short). Verify that validation error messages appear.
  • Test the registration form at http://127.0.0.1:3000/register with both valid and invalid data (mismatched emails, weak passwords, etc.).
  • Test the login form at http://127.0.0.1:3000/login with correct credentials, incorrect credentials, and missing fields.
  • If you are logged in as a user, test the account edit functionality at http://127.0.0.1:3000/users by clicking edit on your account.

If any validation fails to work or you see errors about missing modules, check your work carefully:

  • Verify that forms.js exports all four validation arrays
  • Confirm that controller files no longer define or export validation arrays
  • Double-check import paths in routes.js (especially the ../ in the middleware import)
  • Make sure you did not accidentally change any validation logic when moving it
Refactoring Best Practices

Good refactoring maintains identical external behavior while improving internal structure. After refactoring, your forms should work exactly as they did before. If behavior changes or validation stops working, you introduced an error during the refactoring process.

Professional developers test thoroughly after refactoring. Work methodically: move one validation array, test it, then move the next. This incremental approach makes it easier to identify and fix problems quickly.

Part 2: Adding Faculty Profile Images

Your faculty directory currently displays names and information but lacks visual interest. Adding profile images makes the directory more engaging and helps students and visitors recognize faculty members. You will add images to both the faculty list page and the individual faculty detail pages.

Understanding Image Naming Convention

The faculty images you will download are named to match each faculty member's slug property with .jpg appended to the end. For example, if a faculty member has a slug of john-smith, their image file is named john-smith.jpg. This consistent naming convention allows you to construct image paths dynamically in your templates.

Since each faculty object already includes a slug property, you can build the complete image path by combining the base path /images/faculty/ with the slug and the .jpg extension.

Implementation Instructions

1. Download and Install Faculty Images

Download the faculty images archive: faculty-images.zip. Extract the archive and place all image files into public/images/faculty/.

2. Add Images to Faculty List Page

Open src/views/faculty/list.ejs. You need to add an <img> element for each faculty member in the list.

Each faculty member in your loop has a slug property. Use this slug to construct the image source path. Remember that all images are located at /images/faculty/[slug].jpg where [slug] is replaced with the actual slug value. Don't forget to add appropriate alt text for accessibility using the faculty member's name.

You should spend some time considering the best placement for the image within the faculty list item. Think about how the image will interact with the existing text content and ensure it enhances the overall design without overwhelming it. The following is the bare minimum CSS you should add, it includes a cleaver trick to make missing images display a placeholder:


            .faculty-card {
                position: relative;
            }

            .faculty-photo {
                width: 100%;
                aspect-ratio: 1 / 1;
                object-fit: cover;
                display: block;
                margin-bottom: 7px;
                background: url('https://placehold.co/480') center / cover no-repeat;
            }
        

3. Add Images to Faculty Detail Page

Open src/views/faculty/detail.ejs. Add a faculty profile image to this page using the same pattern you used for the list page. Remember the detail page already receives a facultyMember object that contains the slug property. Don't forget to update the CSS for this page as well to ensure the image displays nicely alongside the faculty member's details.

4. Test Your Implementation

Restart your server if needed and test your faculty pages:

  • Visit http://127.0.0.1:3000/faculty and verify that all faculty members display profile images
  • Click on individual faculty members to view their detail pages and verify images appear
  • Test the sorting functionality on the faculty list page to ensure images still display correctly when sorted

If images fail to load, open your browser's developer tools and check the Console tab for 404 errors. These errors will show you the exact path the browser is trying to load, which helps identify if your path construction is incorrect or if image files are missing or misnamed.

Make sure the image files are in public/images/faculty/ and not in a nested subdirectory. The public folder is your web root, so /images/faculty/john-smith.jpg in your HTML corresponds to public/images/faculty/john-smith.jpg in your file system.

Add Image Styling

Add CSS rules to public/css/faculty.css to style the faculty images appropriately. Without styling, images may appear too large or inconsistently sized across different faculty members.

Consider how you want the images to display: consistent sizing, borders, spacing, or other visual treatments. Your styling should ensure images look professional on both the list and detail pages.

Reflection and Next Steps

This refactoring assignment challenged you to work more independently while applying professional organizational patterns. You reorganized code to improve architectural clarity, demonstrating that you understand not just how to write code, but how to organize it for maintainability and growth.

The validation refactoring taught you to recognize when architectural patterns should evolve. Small applications benefit from simple organization, but growing applications need stricter separation of concerns. Professional developers make these decisions based on project needs, team size, and expected growth.

Adding faculty images reinforced your understanding of the complete MVC cycle: models provide data, controllers coordinate between layers, and views render that data into HTML. You worked with file systems, dynamic path construction, and the importance of naming conventions in creating maintainable code.

As you move into your final project, apply these organizational principles from the beginning. Structure your code for growth even if you start small. Separate concerns clearly. Make naming conventions consistent. These practices distinguish professional code from beginner code.