Skip to content

Software Architecture

Kyle Morel edited this page Dec 15, 2025 · 3 revisions

This page outlines the general architecture and design principles of the Permit Connect Navigator Service (PCNS). It is mainly intended for a technical audience, and for people who want to have a better understanding of how the system works.

Table of Contents

Infrastructure

3rd Party APIs

  • Common Object Management Service (COMS) (ref)
  • BC Address Geocoder (ref]
  • OpenStreet Map (ref]
  • Orgbook (ref]
  • ATS
  • Common Hosted Email Service (ref]
  • NR Permitting Exchange, Aggregation and Collection Hub (PEACH) (ref]

Database Schema

The PostgreSQL database is written and handled via managed, code-first migrations. We generally store tables containing activities, initiatives, enquiries, permits, projects, users, and how they relate to each other. We enforce foreign key integrity by invoking onUpdate and onDelete cascades in Postgres. This ensures that we do not have dangling references when entries are removed from the system.

The following figures depict the database schema structure as of Dec 2025.

Auditing

We use a generic audit schema table to track any update and delete operations done on the database. This table is only modified by database via table triggers, and is not accessible by the PCNS application itself. This meets most general security, tracking and auditing requirements.

Code Design

Organization – Backend

The backend code structure in PCNS follows a simple, layered structure adhering to best practice recommendations from Express, Node, and TypeScript coding styles, and utilizes ESLint and Prettier to enforce those recommendations. The codebase has the following discrete layers:

Layer Purpose
controllers Contains controller express logic for determining what services to invoke and in what order
db Contains the direct database table model definitions and typical modification queries
docs Contains the OpenAPI specification
interfaces Contains the TypeScript interfaces
middleware Contains middleware functions for handling authentication, authorization and feature toggles
parsers Contains parsers for transforming incoming or outgoing data for 3rd party APIs
routes Contains defined Express routes for defining the PCNS API shape and invokes controllers
services Contains logic for interacting with the database other external APIs for specific tasks
types Contains the TypeScript types and domains to transform between
utils Contains useful utilities and functions that are generic and may be used across the codebase
validators Contains logic which examines and enforces incoming request shapes and patterns

Each layer is designed to focus on one specific aspect of business logic. Calls between layers are designed to be deliberate, scoped, and contained. This hopefully makes it easier to tell at a glance what each piece of code is doing and what it depends on. For example, the validation layer sits between the routes and controllers. It ensures that incoming network calls are properly formatted before proceeding with execution.

---
title: Network Request Flow Diagram
---

graph LR
    ST@{ shape: sm-circ, label: "Start" } --> routes
    routes --> middleware
    middleware --> validators
    validators --> controllers
    controllers --> services
    services --> EN@{ shape: fr-circ, label: "End" }
Loading

Middleware and Routing

PCNS middleware focuses on ensuring that the appropriate business logic filters are applied as early as possible. Concerns such as feature toggles, authentication, and authorization are handled here. Express executes middleware in the order of introduction. It will sequentially execute and then invoke the next callback as a part of its call stack. Because of this, we must ensure that the order we introduce and execute our middleware adheres to the following pattern:

  1. Ensure authentication and authorization is valid.
  2. Ensure that the contents, query, and params within the request are structurally sound.
  3. Ensure that the request contents contain only correct data.
  4. Perform any remaining middleware hooks prior to invoking the controller logic.

Controllers and Services

Controllers manage the REST interface responses, focus on the business logic implementation, and invoke the necessary services to perform a business function. Services focus on building the associated SQL query and executing it.

Types and Validators

The types layer solely contains TypeScript types. It is not intended to contain any business logic, but rather to provide a common set of types that can be used across the codebase. The models.ts file leverages the Prisma client autogenerated types to add foreign key relations, and should generally not need to be modified. This helps ensure type safety and consistency in how data is represented. The stuff.d.ts file is for most non autogenerated types that may additionally be required.

The validators layer contains logic that examines and enforces incoming request shapes and patterns. It is responsible for validating the structure of incoming requests, ensuring that they conform to the expected format before they are processed by the controllers. This helps catch errors early in the request lifecycle and provides a clear contract for what data is expected.

Validation leverages the Joi library. This allows us to ensure that both the structure and the content of incoming requests are valid before they reach the business logic layer. The validators are designed to be reusable and composable, allowing for easy extension and modification as the API evolves.

Error Handling

Error handling in PCNS is designed to be consistent and informative. Any thrown errors will fall into one of two categories: known response errors, or unexpected errors. Depending on the type of error classification, we will handle the error differently.

For known response errors, we will leverage the Problem utility to generate a standardized error response compliant with RFC9457. This ensures that all known errors are handled in a consistent manner, providing clear and actionable feedback to the client. For all other unexpected errors, a simple and generic throw new Error() will cover the majority of cases, as these are typically indicative of bugs or issues that need to be addressed in the codebase. This approach allows us to differentiate between expected and unexpected errors, making it easier to debug and maintain the application.

Note

As a convention, only controllers and middleware should throw Problem errors; the rest of the codebase should not throw Problem errors, and instead only throw generic errors. This helps ensure that we only expose and handle network request concerns at the appropriate layers, while keeping the rest of the codebase focused on business logic and data manipulation.

Organization – Frontend

The frontend code structure in PCNS follows a simple, layered structure adhering to best practice recommendations from Vue, and TypeScript coding styles, and utilizes ESLint and Prettier to enforce those recommendations. The codebase has the following discrete layers:

Layer Purpose
assets Contains public facing images and primary CSS designs
components Contains the independent and reusable UI pieces
composables Contains reusable stateful logic functions leveraging Vue's Composition API
interfaces Contains the TypeScript interfaces
lib Contains wrappers around 3rd party package dependenciefs
locales Contains localization files
router Contains defined vue-router routes for defining the PCNS web application shape
services Contains logic for interacting with the PCNS API or other external APIs for specific tasks
store Contains global state and business logic
types Contains the TypeScript types and domains to transform between
utils Contains useful utilities and functions that are generic and may be used across the codebase
validators Contains logic which examines and enforces form and outgoing request shapes and patterns
views Contains top level components directly referenced by the router

Dependencies

The following is a partial list of important package dependencies in the frontend:

Package Purpose
Axios Library for making HTTP requests
Pinia State management framework for Vue
Primevue Vue component and template library
Vite Javascript bundler, hot-module replacement capabilities
Vitest Javascript unit testing framework
Vue Router Client-side routing library for Vue
Vue Test Utils Vue unit test utility library

Clone this wiki locally