Skip to content

Instantly share code, notes, and snippets.

@jph00
Last active December 9, 2024 20:44
Show Gist options
  • Save jph00/4ad7d35ad79013aded41b5ba535a12a3 to your computer and use it in GitHub Desktop.
Save jph00/4ad7d35ad79013aded41b5ba535a12a3 to your computer and use it in GitHub Desktop.

Hypermedia Systems: book summary

This is a summary of "Hypermedia Systems" by Carson Gross, Adam Stepinski, Deniz Akşimşek. Summary created by Jeremy Howard with AI help.

  • Website for the book
  • The revolutionary ideas that empowered the Web
  • A simpler approach to building applications on the Web and beyond with htmx and Hyperview
  • Enhancing web applications without using SPA frameworks

1: Hypermedia: A Reintroduction

Hypermedia:

  • Non-linear media with branching
  • Hypermedia controls enable interactions, often with remote servers
  • Example: Hyperlinks in HTML

Brief History:

  • 1945: Vannevar Bush's Memex concept
  • 1963: Ted Nelson coins "hypertext" and "hypermedia"
  • 1968: Douglas Engelbart's "Mother of All Demos"
  • 1990: Tim Berners-Lee creates first website
  • 2000: Roy Fielding's REST dissertation

HTML as Hypermedia:

  1. Anchor tags:
    <a href="https://hypermedia.systems/">Hypermedia Systems</a>
  2. Form tags:
    <form action="/signup" method="post">
      <input type="text" name="email" placeholder="Enter Email To Sign Up">
      <button>Sign Up</button>
    </form>

Non-Hypermedia Approach:

  • JSON Data APIs
  • Example:
    fetch('/api/v1/contacts/1')
      .then(response => response.json())
      .then(data => updateUI(data))

Single Page Applications (SPAs):

  • Use JavaScript for UI updates
  • Communicate with server via JSON APIs
  • Example frameworks: React, Angular, Vue.js

Hypermedia Advantages:

  • Simplicity
  • Tolerance to content and API changes
  • Leverages browser features (e.g., caching)

Hypermedia-Oriented Libraries:

  • Enhance HTML as hypermedia
  • Example: htmx
    <button hx-get="/contacts/1" hx-target="#contact-ui">
      Fetch Contact
    </button>

When to Use Hypermedia:

  • Websites with moderate interactivity needs
  • Server-side focused applications
  • CRUD operations

When Not to Use Hypermedia:

  • Highly dynamic UIs (e.g., spreadsheet applications)
  • Applications requiring frequent, complex updates

HTML Notes:

  • Avoid <div> soup
  • Use semantic HTML elements
  • Example of poor practice:
    <div class="bg-accent padding-4 rounded-2" onclick="doStuff()">
      Do stuff
    </div>
  • Improved version:
    <button class="bg-accent padding-4 rounded-2" onclick="doStuff()">
      Do stuff
    </button>

2. Components Of A Hypermedia System

Components of a Hypermedia System:

  1. Hypermedia:

    • HTML as primary example
    • Hypermedia controls: anchors and forms
    • URLs: [scheme]://[userinfo]@[host]:[port][path]?[query]#[fragment] Example: https://hypermedia.systems/book/contents/
  2. Hypermedia Protocols:

    • HTTP methods: GET, POST, PUT, PATCH, DELETE
    • HTTP response codes with examples: 200 OK: Successful request 301 Moved Permanently: Resource has new permanent URL 404 Not Found: Resource doesn't exist 500 Internal Server Error: Server-side error
    • Caching: Cache-Control and Vary headers
  3. Hypermedia Servers:

    • Can be built in any programming language
    • Flexibility in choosing backend technology (HOWL stack)
  4. Hypermedia Clients:

    • Web browsers as primary example
    • Importance of uniform interface

REST Constraints:

  1. Client-Server Architecture

  2. Statelessness:

    • Every request contains all necessary information
    • Session cookies as a common violation
  3. Caching:

    • Supported through HTTP headers
  4. Uniform Interface: a. Identification of resources (URLs) b. Manipulation through representations c. Self-descriptive messages d. Hypermedia As The Engine of Application State (HATEOAS)

    HATEOAS example showing adaptation to state changes:

    <!-- Active contact -->
    <div>
      <h1>Joe Smith</h1>
      <div>Status: Active</div>
      <a href="/contacts/42/archive">Archive</a>
    </div>
    
    <!-- Archived contact -->
    <div>
      <h1>Joe Smith</h1>
      <div>Status: Archived</div>
      <a href="/contacts/42/unarchive">Unarchive</a>
    </div>
  5. Layered System:

    • Allows intermediaries like CDNs
  6. Code-On-Demand (optional):

    • Allows client-side scripting

Advantages of Hypermedia-Driven Applications:

  • Reduced API versioning issues
  • Improved system adaptability
  • Self-documenting interfaces

Limitations:

  • May not be ideal for applications requiring extensive client-side interactivity

HTML Notes:

  • Use semantic elements carefully (e.g., <section>, <article>, <nav>)
  • Refer to HTML specification for proper usage
  • Sometimes <div> is appropriate

Practical implications of REST constraints:

  1. Statelessness: Improves scalability but can complicate user sessions.
  2. Caching: Reduces server load and improves response times.
  3. Uniform Interface: Simplifies client-server interaction but may increase payload size.

Hypermedia vs JSON-based APIs:

  1. Flexibility: Hypermedia adapts to changes without client updates; JSON APIs often require versioning.
  2. Discoverability: Hypermedia exposes available actions; JSON requires separate documentation.
  3. Payload size: Hypermedia typically larger due to including UI elements.

Examples where hypermedia might not be ideal:

  1. Real-time applications (e.g., chat systems) requiring frequent, small updates.
  2. Complex data visualization tools needing extensive client-side processing.
  3. Offline-first mobile apps that require local data manipulation.

3. A Web 1.0 Application

To start our journey into Hypermedia-Driven Applications, we consider a simple contact management web application called Contact.app. Contact.app is a web 1.0-style contact management application built with Python, Flask, and Jinja2 templates. It implements CRUD operations for contacts using a RESTful architecture.

Key components:

  1. Flask routes:

    • GET /contacts: List all contacts or search
    • GET /contacts/new: New contact form
    • POST /contacts/new: Create new contact
    • GET /contacts/: View contact details
    • GET /contacts//edit: Edit contact form
    • POST /contacts//edit: Update contact
    • POST /contacts//delete: Delete contact
  2. Jinja2 templates:

    • index.html: Contact list and search form
    • new.html: New contact form
    • show.html: Contact details
    • edit.html: Edit contact form
  3. Contact model (implementation details omitted)

The application uses the Post/Redirect/Get pattern for form submissions to prevent duplicate submissions on page refresh.

Example route handler for creating a new contact:

@app.route("/contacts/new", methods=['POST'])
def contacts_new():
    c = Contact(
      None,
      request.form['first_name'],
      request.form['last_name'],
      request.form['phone'],
      request.form['email'])
    if c.save():
        flash("Created New Contact!")
        return redirect("/contacts")
    else:
        return render_template("new.html", contact=c)

This handler creates a new Contact object, attempts to save it, and either redirects to the contact list with a success message or re-renders the form with error messages.

Example template snippet (edit.html):

<form action="/contacts/{{ contact.id }}/edit" method="post">
  <fieldset>
    <legend>Contact Values</legend>
    <p>
      <label for="email">Email</label>
      <input name="email" id="email" type="text"
        placeholder="Email" value="{{ contact.email }}">
      <span class="error">{{ contact.errors['email'] }}</span>
    </p>
    <p>
      <label for="first_name">First Name</label>
      <input name="first_name" id="first_name" type="text"
        placeholder="First Name" value="{{ contact.first }}">
      <span class="error">{{ contact.errors['first'] }}</span>
    </p>
    <!-- Other fields omitted -->
    <button>Save</button>
  </fieldset>
</form>

<form action="/contacts/{{ contact.id }}/delete" method="post">
  <button>Delete Contact</button>
</form>

This template renders a form for editing a contact, including error message display. It also includes a separate form for deleting the contact.

The application demonstrates RESTful principles and HATEOAS through hypermedia exchanges. Each response contains the necessary links and forms for the client to navigate and interact with the application, without requiring prior knowledge of the API structure.

While functional, the application lacks modern interactivity. Every action results in a full page reload, which can feel clunky to users accustomed to more dynamic interfaces. This sets the stage for improvement using htmx in subsequent chapters, which will enhance the user experience while maintaining the hypermedia-driven architecture.

Don't overuse of generic elements like <div> and <span>, which can lead to "div soup". Instead, the use of semantic HTML is encouraged for better accessibility, readability, and maintainability. For example, using <article>, <nav>, or <section> tags where appropriate, rather than generic <div> tags.

By using HTML as the primary representation, the application inherently follows REST principles without the need for complex API versioning or extensive client-side logic to interpret application state.

4. Extending HTML As Hypermedia

Htmx extends HTML as a hypermedia, addressing limitations of traditional HTML:

  1. Any element can make HTTP requests
  2. Any event can trigger requests
  3. All HTTP methods are available
  4. Content can be replaced anywhere on the page

Core htmx attributes:

  • hx-get, hx-post, hx-put, hx-patch, hx-delete: Issue HTTP requests
  • hx-target: Specify where to place the response
  • hx-swap: Define how to swap in new content
  • hx-trigger: Specify events that trigger requests

Htmx expects HTML responses, not JSON. This allows for partial content updates without full page reloads.

Passing request parameters:

  1. Enclosing forms: Values from ancestor form elements are included
  2. hx-include: Specify inputs to include using CSS selectors
  3. hx-vals: Include static or dynamic values

Example:

<button hx-get="/contacts" hx-target="#main" hx-swap="outerHTML"
  hx-trigger="click, keyup[ctrlKey && key == 'l'] from:body">
  Get The Contacts
</button>

This button loads contacts when clicked or when Ctrl+L is pressed anywhere on the page.

Browser history support:

  • hx-push-url: Create history entries for htmx requests

Example of including values:

<button hx-get="/contacts" hx-vals='{"state":"MT"}'>
  Get The Contacts In Montana
</button>

Dynamic values can be included using the js: prefix:

<button hx-get="/contacts"
  hx-vals='js:{"state":getCurrentState()}'>
  Get The Contacts In The Selected State
</button>

Htmx enhances HTML's capabilities while maintaining simplicity and declarative nature. It allows for more interactive web applications without abandoning the hypermedia model, bridging the gap between traditional web applications and Single Page Applications.

When using htmx with history support, handle both partial and full-page responses for refreshed pages. This can be done using HTTP headers, a topic covered later.

HTML quality is important. Avoid incorrect HTML and use semantic elements when possible. However, maintaining high-quality markup in large-scale, internationalized websites can be challenging due to the separation of content authors and developers, and differing stylistic conventions between languages.

5. Htmx Patterns

Installing htmx: Download from unpkg.com and save to static/js/htmx.js, or use the CDN:

<script src="https://unpkg.com/htmx.org@next/dist/htmx.min.js"></script>

AJAX-ifying the application: Use hx-boost="true" on the body tag to convert all links and forms to AJAX-powered controls:

<body hx-boost="true">

Boosted links: Convert normal links to AJAX requests, replacing only the body content. Example:

<a href="/contacts">Contacts</a>

This link now uses AJAX, avoiding full page reloads.

Boosted forms: Similar to boosted links, forms use AJAX requests instead of full page loads. No changes needed to HTML.

Attribute inheritance: Place hx-boost on a parent element to apply to all children:

<div hx-boost="true">
  <a href="/contacts">Contacts</a>
  <a href="/settings">Settings</a>
</div>

Progressive enhancement: Boosted elements work without JavaScript, falling back to normal behavior. No additional code needed.

Deleting contacts with HTTP DELETE: Use hx-delete attribute on a button to issue DELETE requests:

<button hx-delete="/contacts/{{ contact.id }}">Delete Contact</button>

Updating server-side code: Modify route to handle DELETE requests:

@app.route("/contacts/<contact_id>", methods=["DELETE"])
def contacts_delete(contact_id=0):
    # Delete contact logic here

Response code gotcha: Use 303 redirect to ensure proper GET request after deletion:

return redirect("/contacts", 303)

Targeting the right element: Use hx-target to specify where to place the response:

<button hx-delete="/contacts/{{ contact.id }}" hx-target="body">Delete Contact</button>

Updating location bar URL: Add hx-push-url="true" to update browser history:

<button hx-delete="/contacts/{{ contact.id }}" hx-target="body" hx-push-url="true">Delete Contact</button>

Confirmation dialog: Use hx-confirm for delete operations:

<button hx-delete="/contacts/{{ contact.id }}" hx-confirm="Are you sure?">Delete Contact</button>

Validating contact emails: Implement inline validation using htmx:

<input name="email" type="email"
  hx-get="/contacts/{{ contact.id }}/email"
  hx-target="next .error">
<span class="error"></span>

Updating input type: Change input type to "email" for basic browser validation:

<input name="email" type="email">

Inline validation: Use hx-get to trigger server-side validation on input change (see example above).

Validating emails server-side: Create endpoint to check email uniqueness:

@app.route("/contacts/<contact_id>/email", methods=["GET"])
def contacts_email_get(contact_id=0):
    # Email validation logic here

Improving user experience: Validate email as user types using keyup event:

<input name="email" type="email"
  hx-get="/contacts/{{ contact.id }}/email"
  hx-target="next .error"
  hx-trigger="change, keyup">

Debouncing validation requests: Add delay to avoid excessive requests during typing:

<input name="email" type="email"
  hx-get="/contacts/{{ contact.id }}/email"
  hx-target="next .error"
  hx-trigger="change, keyup delay:200ms">

Ignoring non-mutating keys: Use changed modifier to only trigger on value changes:

<input name="email" type="email"
  hx-get="/contacts/{{ contact.id }}/email"
  hx-target="next .error"
  hx-trigger="change, keyup delay:200ms changed">

Paging: Implement basic paging for contact list:

<a href="/contacts?page={{ page + 1 }}">Next</a>

Click to load: Create a button to load more contacts inline:

<button hx-get="/contacts?page={{ page + 1 }}"
  hx-target="closest tr"
  hx-swap="outerHTML"
  hx-select="tbody > tr">
  Load More
</button>

Infinite scroll: Use hx-trigger="revealed" to load more contacts automatically:

<span hx-get="/contacts?page={{ page + 1 }}"
  hx-trigger="revealed"
  hx-target="closest tr"
  hx-swap="outerHTML"
  hx-select="tbody > tr">
  Loading More...
</span>

HTML notes on modals: Use modals cautiously, as they can complicate hypermedia approach. Consider inline editing or separate pages instead.

Caution with "display: none": Be aware of accessibility implications when hiding elements. It removes elements from the accessibility tree and keyboard focus.

Visually hidden class: Use a utility class to hide elements visually while maintaining accessibility:

.vh {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  block-size: 1px;
  inline-size: 1px;
  overflow: hidden;
  white-space: nowrap;
}

6. More Htmx Patterns

Active Search:

  • Implement search-as-you-type using hx-get, hx-trigger, and hx-target.
  • Use hx-trigger="search, keyup delay:200ms changed" for debouncing.
  • Target specific elements with hx-target="tbody".
  • Use HTTP headers like HX-Trigger for server-side logic.
  • Factor templates for reusability (e.g., rows.html).
  • Update URL with hx-push-url="true".
  • Add request indicators using hx-indicator attribute.

Example:

<input id="search" type="search" name="q"
  hx-get="/contacts"
  hx-trigger="search, keyup delay:200ms changed"
  hx-target="tbody"
  hx-push-url="true"
  hx-indicator="#spinner">
<img id="spinner" class="htmx-indicator" src="/static/img/spinning-circles.svg">

Lazy Loading:

  • Defer expensive operations using hx-get and hx-trigger="load".
  • Example: Lazy loading contact count.
<span hx-get="/contacts/count" hx-trigger="load">
  <img class="htmx-indicator" src="/static/img/spinning-circles.svg"/>
</span>
  • Use internal indicators for one-time requests.
  • Implement truly lazy loading with hx-trigger="revealed".

Inline Delete:

  • Add delete functionality to contact rows.
  • Use hx-delete, hx-confirm, and hx-target attributes.
  • Differentiate between delete button and inline delete using HX-Trigger header.
  • Implement fade-out effect using CSS transitions and htmx-swapping class.
  • Adjust swap timing with hx-swap="outerHTML swap:1s".

Example:

<a href="#" hx-delete="/contacts/{{ contact.id }}"
  hx-swap="outerHTML swap:1s"
  hx-confirm="Are you sure you want to delete this contact?"
  hx-target="closest tr">Delete</a>

CSS for fade-out:

tr.htmx-swapping {
  opacity: 0;
  transition: opacity 1s ease-out;
}

Bulk Delete:

  • Add checkboxes to rows for selection.
  • Create a "Delete Selected Contacts" button with hx-delete="/contacts".
  • Enclose table in a form to include selected contact IDs.
  • Update server-side code to handle multiple deletions.

Example:

<form>
  <table>
    <!-- Table content -->
  </table>
  <button
    hx-delete="/contacts"
    hx-confirm="Are you sure you want to delete these contacts?"
    hx-target="body">
    Delete Selected Contacts
  </button>
</form>

Server-side code:

@app.route("/contacts/", methods=["DELETE"])
def contacts_delete_all():
    contact_ids = [int(id) for id in request.form.getlist("selected_contact_ids")]
    for contact_id in contact_ids:
        contact = Contact.find(contact_id)
        contact.delete()
    flash("Deleted Contacts!")
    contacts_set = Contact.all()
    return render_template("index.html", contacts=contacts_set)

7. A Dynamic Archive UI

(This chapter is not included)

8. Tricks Of The Htmx Masters

Htmx Attributes:

  • hx-swap: Controls content swapping. Options include innerHTML, outerHTML, beforebegin, afterend.
  • Modifiers: settle, show, scroll, focus-scroll. Example:
<button hx-get="/contacts" hx-target="#content-div"
  hx-swap="innerHTML show:body:top">
  Get Contacts
</button>
  • hx-trigger: Specifies events triggering requests. Modifiers: delay, changed, once, throttle, from, target, consume, queue.
  • Trigger filters: Use JavaScript expressions in square brackets. Example:
<button hx-get="/contacts"
  hx-trigger="click[contactRetrievalEnabled()]">
  Get Contacts
</button>
  • Synthetic events: load, revealed, intersect.

Other Attributes:

  • hx-push-url: Updates navigation bar URL.
  • hx-preserve: Keeps original content between requests.
  • hx-sync: Synchronizes requests between elements.
  • hx-disable: Disables htmx behavior.

Events:

  • Htmx-generated events: htmx:load, htmx:configRequest, htmx:afterRequest, htmx:abort.
  • Using htmx:configRequest:
document.body.addEventListener("htmx:configRequest", configEvent => {
  configEvent.detail.headers['X-SPECIAL-TOKEN'] = localStorage['special-token'];
})
  • Canceling requests with htmx:abort:
<button id="contacts-btn" hx-get="/contacts" hx-target="body">
  Get Contacts
</button>
<button onclick="document.getElementById('contacts-btn')
  .dispatchEvent(new Event('htmx:abort'))">
  Cancel
</button>
  • Server-generated events using HX-Trigger header.

HTTP Requests & Responses:

  • Headers: HX-Location, HX-Push-Url, HX-Refresh, HX-Retarget.
  • Special response codes: 204 (No Content), 286 (Stop Polling).
  • Customizing response handling:
document.body.addEventListener('htmx:beforeSwap', evt => {
  if (evt.detail.xhr.status === 404) {
    showNotFoundError();
  }
});

Updating Other Content:

  1. Expanding selection
  2. Out of Band Swaps using hx-swap-oob Example:
<table id="contacts-table" hx-swap-oob="true">
  <!-- Updated table content -->
</table>
  1. Events (server-triggered)

Debugging:

  • htmx.logAll(): Logs all internal htmx events.
  • Chrome's monitorEvents(): Monitors all events on an element.
monitorEvents(document.getElementById("some-element"));

Security Considerations:

  • Use hx-disable to ignore htmx attributes in user-generated content.
  • Content Security Policies (CSP): Htmx works with eval() disabled, except for event filters.

Configuring:

  • Use meta tag for configuration:
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>

HTML Notes: Semantic HTML:

  • Focus on writing conformant HTML rather than guessing at "semantic" meanings.
  • Use elements as outlined in the HTML specification.
  • Consider the needs of browsers, assistive technologies, and search engines.

9. Client Side Scripting

Scripting in Hypermedia-Driven Applications:

  1. Is Scripting Allowed?

    • Scripting enhances HTML websites and creates full-fledged client-side applications.
    • Goal: Less code, more readable and hypermedia-friendly code.
  2. Scripting for Hypermedia:

    • Constraints: a. Main data format must be hypermedia. b. Minimize client-side state outside the DOM.
    • Focus on interaction design, not business or presentation logic.
  3. Scripting Tools:

    • VanillaJS: Plain JavaScript without frameworks.
    • Alpine.js: JavaScript library for adding behavior directly in HTML.
    • _hyperscript: Non-JavaScript scripting language created alongside htmx.
  4. Vanilla JavaScript:

    • Simple counter example:
      <section class="counter">
        <output id="my-output">0</output>
        <button onclick="document.querySelector('#my-output').textContent++">
          Increment
        </button>
      </section>
    • RSJS (Reasonable System for JavaScript Structure):
      <section class="counter" data-counter>
        <output data-counter-output>0</output>
        <button data-counter-increment>Increment</button>
      </section>
      document.querySelectorAll("[data-counter]").forEach(el => {
        const output = el.querySelector("[data-counter-output]"),
              increment = el.querySelector("[data-counter-increment]");
        increment.addEventListener("click", e => output.textContent++);
      });
  5. Alpine.js:

    • Counter example:
      <div x-data="{ count: 0 }">
        <output x-text="count"></output>
        <button x-on:click="count++">Increment</button>
      </div>
    • Bulk action toolbar:
      <form x-data="{ selected: [] }">
        <template x-if="selected.length > 0">
          <div class="tool-bar">
            <span x-text="selected.length"></span> contacts selected
            <button @click="selected = []">Cancel</button>
            <button @click="confirm(`Delete ${selected.length} contacts?`) &&
              htmx.ajax('DELETE', '/contacts', 
              { source: $root, target: document.body })">
              Delete
            </button>
          </div>
        </template>
      </form>
  6. _hyperscript:

    • Counter example:
      <div class="counter">
        <output>0</output>
        <button _="on click
          increment the textContent of the previous <output/>">
          Increment
        </button>
      </div>
    • Keyboard shortcut:
      <input id="search" name="q" type="search" placeholder="Search Contacts"
        _="on keydown[altKey and code is 'KeyS'] from the window
          focus() me">
  7. Off-the-Shelf Components:

    • Integration options: a. Callbacks:
        <button @click="Swal.fire({
          title: 'Delete these contacts?',
          showCancelButton: true,
          confirmButtonText: 'Delete'
        }).then((result) => {
          if (result.isConfirmed) htmx.ajax('DELETE', '/contacts',
            { source: $root, target: document.body })
        });">Delete</button>
 b. Events:
        function sweetConfirm(elt, config) {
          Swal.fire(config)
            .then((result) => {
              if (result.isConfirmed) {
                elt.dispatchEvent(new Event('confirmed'));
              }
            });
        }
        <button hx-delete="/contacts" hx-target="body" hx-trigger="confirmed"
          @click="sweetConfirm($el, {
            title: 'Delete these contacts?',
            showCancelButton: true,
            confirmButtonText: 'Delete'
          })">Delete</button>
  1. Pragmatic Scripting:

    • Choose the right tool for the job.
    • Avoid JSON data APIs for server communication.
    • Minimize state outside the DOM.
    • Favor events over hard-coded callbacks.
  2. HTML Notes:

    • HTML is suitable for both documents and applications.
    • Hypermedia allows for interactive controls within documents.
    • HTML's interactive capabilities need further development.

10. JSON Data APIs

Hypermedia APIs & JSON Data APIs:

  1. Differences:

    • Hypermedia API: Flexible, no versioning needed, specific to application needs.
    • JSON Data API: Stable, versioned, rate-limited, general-purpose.
  2. Adding JSON Data API to Contact.app:

    • Root URL: /api/v1/
    • Listing contacts:
      @app.route("/api/v1/contacts", methods=["GET"])
      def json_contacts():
          contacts_set = Contact.all()
          contacts_dicts = [c.__dict__ for c in contacts_set]
          return {"contacts": contacts_dicts}
  3. Adding contacts:

    @app.route("/api/v1/contacts", methods=["POST"])
    def json_contacts_new():
        c = Contact(None,
          request.form.get('first_name'),
          request.form.get('last_name'),
          request.form.get('phone'),
          request.form.get('email'))
        if c.save():
            return c.__dict__
        else:
            return {"errors": c.errors}, 400
  4. Viewing contact details:

    @app.route("/api/v1/contacts/<contact_id>", methods=["GET"])
    def json_contacts_view(contact_id=0):
        contact = Contact.find(contact_id)
        return contact.__dict__
  5. Updating contacts:

    @app.route("/api/v1/contacts/<contact_id>", methods=["PUT"])
    def json_contacts_edit(contact_id):
        c = Contact.find(contact_id)
        c.update(
            request.form['first_name'],
            request.form['last_name'],
            request.form['phone'],
            request.form['email'])
        if c.save():
            return c.__dict__
        else:
            return {"errors": c.errors}, 400
  6. Deleting contacts:

    @app.route("/api/v1/contacts/<contact_id>", methods=["DELETE"])
    def json_contacts_delete(contact_id=0):
        contact = Contact.find(contact_id)
        contact.delete()
        return jsonify({"success": True})
  7. Additional considerations:

    • Rate limiting
    • Authentication (token-based for JSON API, session cookies for web app)
    • Pagination
    • Proper error handling (e.g., 404 responses)
  8. API "Shape" differences:

    • JSON API: Fewer endpoints, no need for UI-specific routes
    • Hypermedia API: More flexible, can change without breaking clients
  9. Model View Controller (MVC) Paradigm:

    • Model: Domain logic
    • View: HTML representation
    • Controller: Thin layer connecting HTTP requests to model
  10. HTML Notes - Microformats:

    • Embed machine-readable data in HTML using classes
    • Example:
      <a class="h-card" href="https://john.example">
        <img src="john.jpg" alt=""> John Doe
      </a>
    • Parsed result:
      {
        "type": ["h-card"],
        "properties": {
          "name": ["John Doe"],
          "photo": ["john.jpg"],
          "url": ["https://john.example"]
        }
      }
    • Useful for cross-website systems like IndieWeb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment