bhylu.com

Advertisements

AngularJS SQL and API Boundaries

A browser application should request data through a service boundary, not connect directly to a SQL database. AngularJS does not make database credentials safe to place in downloaded JavaScript.

How it works

This lesson represents a server-owned repair listing with a read-only local API. The controller checks the received collection shape before using it. A real backend would own authentication, parameterized SQL, authorization and error handling. The fixture does not run SQL and does not claim to demonstrate a database transaction; its purpose is to make the client/server boundary explicit.

Run the code

AngularJS 1.8.3 is a legacy framework whose official support ended in January 2022. These examples teach maintenance, not a recommendation for a new application. They need a separate local browser page; Bhylu has no AngularJS playground.

In an empty folder save the HTML below as api-boundary.html and the server as lab-server.mjs. Make a vendor folder and save the four pinned libraries there, retaining their MIT notices. With Node 22 or later run node lab-server.mjs api-boundary.html, then open http://127.0.0.1:8136/. Stop that lab before running a different lesson. The server supplies fixed read-only HTTP fixtures; no data is saved and it does not connect to SQL. No PHP, Apache or MySQL configuration changes are needed.

Example

api-boundary.html

<!doctype html>
<html lang="en" ng-app="repairLab" ng-strict-di>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>AngularJS SQL and API Boundaries</title>
    <style>
      :root {
        font:
          18px/1.55 system-ui,
          sans-serif;
        color: #0f3464;
        background: #fff;
        color-scheme: light;
      }
      * {
        box-sizing: border-box;
      }
      body {
        margin: 0;
      }
      main {
        max-width: 50rem;
        margin: auto;
        padding: 1.2rem;
      }
      h1 {
        font-size: 1.65rem;
      }
      label {
        display: block;
        margin: 0.6rem 0;
      }
      input,
      select,
      textarea,
      button {
        font: inherit;
        max-width: 100%;
        padding: 0.35rem;
        margin: 0.2rem;
      }
      button {
        border: 1px solid #034fa0;
        border-radius: 0.25rem;
        background: #034fa0;
        color: white;
      }
      button:disabled {
        opacity: 0.55;
      }
      output,
      [role="status"] {
        display: block;
        min-height: 1.5rem;
      }
      table {
        border-collapse: collapse;
        width: 100%;
      }
      td,
      th {
        padding: 0.4rem;
        border: 1px solid #a5bbd4;
        text-align: left;
      }
      pre {
        white-space: pre-wrap;
        overflow-wrap: anywhere;
      }
      :focus-visible {
        outline: 3px solid #0078d4;
        outline-offset: 3px;
      }
      [ng-cloak] {
        display: none !important;
      }
      .marked {
        font-weight: 700;
      }
      .error {
        color: #a02424;
      }
      :root[data-theme="dark"] {
        color: #d6e7fb;
        background: #061d30;
        color-scheme: dark;
      }
      :root[data-theme="dark"] .error {
        color: #ffb4b4;
      }
    </style>
  </head>
  <body>
    <main ng-controller="DeskController as vm" ng-cloak>
      <h1>Community repair desk</h1>
      <p>AngularJS 1.8.3 · Legacy maintenance lab · Local demonstration</p>
      <button type="button" ng-click="vm.load()">Load repair list</button>
      <ul>
        <li ng-repeat="row in vm.rows track by row.id">{{ row.note }}</li>
      </ul>
      <p data-result>{{ vm.message }}</p>
    </main>
    <script src="/vendor/angular.min.js"></script>
    <script>
      (function () {
        "use strict";
        var app = angular.module("repairLab", []);
        app.controller("DeskController", [
          "$http",
          function ($http) {
            var vm = this;
            vm.rows = [];
            vm.message = "Not loaded";
            vm.load = function () {
              return $http.get("/api/repairs").then(
                function (response) {
                  var rows = response.data;
                  if (
                    !Array.isArray(rows) ||
                    !rows.every(function (row) {
                      return (
                        row &&
                        typeof row.id === "string" &&
                        typeof row.note === "string"
                      );
                    })
                  ) {
                    vm.message = "Invalid response";
                    return;
                  }
                  vm.rows = rows;
                  vm.message = rows.length + " records from API";
                },
                function () {
                  vm.message = "Request failed";
                },
              );
            };
          },
        ]);
      })();
    </script>
  </body>
</html>

Focus on the bindings and controller

This excerpt leaves out the document shell, styles and library files. Use the complete download to run it.


<button type="button" ng-click="vm.load()">Load repair list</button>
<ul>
  <li ng-repeat="row in vm.rows track by row.id">{{ row.note }}</li>
</ul>
<p data-result>{{ vm.message }}</p>
<script>
  (function () {
    "use strict";
    var app = angular.module("repairLab", []);
    app.controller("DeskController", [
      "$http",
      function ($http) {
        var vm = this;
        vm.rows = [];
        vm.message = "Not loaded";
        vm.load = function () {
          return $http.get("/api/repairs").then(
            function (response) {
              var rows = response.data;
              if (
                !Array.isArray(rows) ||
                !rows.every(function (row) {
                  return (
                    row &&
                    typeof row.id === "string" &&
                    typeof row.note === "string"
                  );
                })
              ) {
                vm.message = "Invalid response";
                return;
              }
              vm.rows = rows;
              vm.message = rows.length + " records from API";
            },
            function () {
              vm.message = "Request failed";
            },
          );
        };
      },
    ]);
  })();
</script>

Expected result

Not loaded

Loading produces two controlled records from the fixture. That checks a real HTTP data flow, not a database connection. A production validator should also enforce uniqueness, sizes and the domain constraints used by its operations before rendering or updating records.

Advertisements