Back to the Future: Exploring Frameworkless and Serverless Web Development

restdbjones

Jbee - codehooks.io

Posted on December 11, 2023

Back to the Future: Exploring Frameworkless and Serverless Web Development

Introduction

Hello Web Developers!

In this blog, we'll cover how to set up a simple web development environment, effectively use Tailwind CSS and DaisyUI for styling, and create serverless dynamic web pages using the codehooks.io backend/database service without the complexities of SPA frameworks like React.

You'll learn about server side HTML pages, handling form submissions and integrating pages with a database, making your web applications both interactive and efficient.

This guide is a practical walkthrough for those who want to explore a simpler yet powerful approach to building responsive web applications.

codehooks.io is a new and simplified backend-as-a-service to create complete API backends with JavaScript / Node.JS. Enjoy smooth and fast backend development with ZERO config serverless JavaScript/TypeScript/Node.js with integrated NoSQL document Database, Key-Value store, CRON Jobs and Queue Workers.

With this project you will create a complete application for a dynamic web site. You can use it as a starting point for creating your own web site or any web app indeed.

The screenshots below gives you a quick look at how the resulting web site looks like.

Home page

About page

Mobile view

Link to a live example: https://youthful-levy-7b1f.codehooks.io.

To setup your own domain, just add an A record in your DNS service and point it to the Codehooks.io domain IP service.

Setting up your project in Codehooks

We'll use the Codehooks CLI to build and deploy our web application.

npm i -g codehooks
Enter fullscreen mode Exit fullscreen mode

Download a copy of the example code from Github.

git clone https://github.com/RestDB/codehooks-io-examples.git
Enter fullscreen mode Exit fullscreen mode

Change to your local directory dynamicweb. (Notice that this repo also have a lot of other examples to study at a later time)

cd codehooks-io-examples/dynamicweb
Enter fullscreen mode Exit fullscreen mode

Connect this project code to your own project that you've created in Codehooks Studio (you'll need to sign up for a free account). Remember to coho login to your account first.

coho init --empty
Enter fullscreen mode Exit fullscreen mode

Pick your project from the presented list, a config.json is created to connect this code to your project for later deployment.

Install all dependencies.

npm install
Enter fullscreen mode Exit fullscreen mode

You should now see a similar project structure where the config.json should contain the name of your project and environment.

.
├── assets
│   └── img
│       ├── logo-small.png
│       ├── logo.png
│       ├── persona1.png
│       ├── persona2.png
│       ├── persona3.png
│       └── persona4.png
├── exampledata
│   └── products.json
├── views
│   ├── about.hbs
│   ├── contact.hbs
│   ├── home.hbs
│   ├── layout.hbs
│   ├── productDetails.hbs
│   ├── products.hbs
│   ├── services.hbs
│   └── thanks.hbs
├── README.md
├── config.json
├── index.js
└── package.json
Enter fullscreen mode Exit fullscreen mode

Application overview: index.js

Here's the basic structure of our serverless web application in the index.js JavaScript:

// Importing essential modules
import { app, datastore } from 'codehooks-js';
import handlebars from 'handlebars';
import layouts from 'handlebars-layouts';

// Setting up the page views and layout template
handlebars.registerHelper(layouts(handlebars));
app.set('views', '/views');
app.set('layout', '/views/layout.hbs');
app.set('view engine', {"hbs": handlebars});

// Configuring public access routes
app.auth(/^\\/(home|about|services|contact|products|product\/.*))?$/, (req, res, next) => { next(); });

// Defining routes and page template to render for different pages
app.get('/about', async (req, res) => {
  res.render('about', {title: "About Us"});
});
// Define other routes...

// Configuring static assets directory
app.static({ route: '/assets', directory: '/assets' });

// Initializing the serverless application
export default app.init();
Enter fullscreen mode Exit fullscreen mode

This skeleton code example sets up the serverless application, configures Handlebars for templating, and defines routing and static asset serving.

The application is deployed by simply running coho run deploy from the project directory. 🙌

Efficient Templating with Handlebars Layouts

Handlebars layout templates promote code reusability and maintain a DRY codebase.

<!-- layout.hbs: Master layout template -->
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
    <!-- Head elements -->
</head>
<body>
    {{{body}}}
    <!-- Common elements -->
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The master layout defines the basic HTML structure for all pages.

{{! products.hbs: Extending the master layout }}
{{#extend "layout"}}
    {{#content "main"}}
    <h1>Our Products</h1>
    <!-- Product-specific content -->
    {{/content}}
{{/extend}}
Enter fullscreen mode Exit fullscreen mode

Individual pages like products.hbs extend the master layout, adding specific content.

Creating Routes and Pages

Define routes for each page in your application. The res.render(...) method generates the html output from a page template, the provided json object from the second argument are dynamic data accessible inside of the Handlebars template as {{some_variable_name}} fields.

// Defining routes and page templates for different pages
// home page
app.get('/', async (req, res) => {  
  res.render('home', {title: "Home page", root, space})
})
// about page
app.get('/about', async (req, res) => {
  res.render('about', {title: "About Us"});
});
// Define other routes...
Enter fullscreen mode Exit fullscreen mode

Create routes for each page, rendering views with Handlebars.

In-Depth Example: Product Listing and Details pages

The product listing page demonstrates dynamic content rendering from the data output of a NoSQL database query. The query fetches all items from the products collection and adds them to the page render context.

// Fetching products from the database and rendering the page
app.get('/products', async (req, res) => {
  // connect to Database
  const conn = await datastore.open();
  // get all products
  const products = await conn.getMany('products', {}).toArray();
  // set product array to the Handlebars context
  res.render('products', { products });
});

// Fetch one product and show product details
app.get('/product/:ID', async (req, res) => {  
  // connect to Database
  const conn = await datastore.open()
  // get the ID URL parameter
  const {ID} = req.params;
  const product = await conn.getOne('products', ID);
  // set product details to Handlebars context for use in view
  res.render('productDetails', {title: "Products detail page", product, root, space})
})
Enter fullscreen mode Exit fullscreen mode

These routes fetches product data from the database and renders it using a Handlebars template.

Import test data

To help in development of the product page you need to import some example data to the database. Take a look in the exampledata/products.json where you'll find a data set of random products. You can import this data data set by running the following CLI command from the project directory:

coho import -f exampledata/products.json -c products
Enter fullscreen mode Exit fullscreen mode

Reads: import the products.json file to the products collection.

A screenshot of the products page with the test data is shown below.

Product page

The Handlebars products.hbs template iterates the product data accordingly. Notice how the product title field links further to the product detail page for a particular database record that matches the product ID - /product/_id.

<div class="space-y-6">
    <!-- Repeat for all products -->
    {{#products}}
        <!-- Product Item -->
        <div class="bg-white rounded shadow flex items-center p-6">
            <div class="flex-1">
                <a href="product/{{_id}}">
                    <h2 class="text-xl font-bold text-gray-800">{{name}}</h2>
                </a>
                <!-- the other fields -->
            </div>
        </div>                
    {{/products}}
</div>
Enter fullscreen mode Exit fullscreen mode

A screenshot of the product details page is shown below.

Product detail page

Handling Form Submissions and Database Uploads

A crucial feature of many web applications is the ability to collect user inputs through forms and subsequently store this data in a database. In our serverless JavaScript application using Codehooks.io and Handlebars, this process can be efficiently managed.

Creating the Contact Form

First, we design a contact form on our web page. This form is created in a Handlebars template (contact.hbs) and typically includes fields like name, email, and a message.

Contact form page

{{! contact.hbs: Contact page with a form }}
{{#extend "layout"}}
    {{#content "main"}}
    <h1>Contact Us</h1>
    <form method="POST" enctype="multipart/form-data">
        <input type="text" name="name" placeholder="Your Name" required>
        <input type="email" name="email" placeholder="Your Email" required>
        <textarea name="message" placeholder="Your Message"></textarea>
        <button type="submit">Send</button>
    </form>
    {{/content}}
{{/extend}}
Enter fullscreen mode Exit fullscreen mode

This form allows users to input their details and message, which will then be sent to the server when submitted.

Server-Side Handling of a form POST

When the user submits the contact form, the server receives the data. In the server-side code, we handle the POST request to the /contact route. This involves parsing the form data using the Busboy library and saving each contact data payload to a collection in the database.

// form post submissison
app.post('/contact', (req, res) => {
  const contactInfo = {};
  const bb = Busboy({ headers: req.headers });
  // read one form field at a time
  bb.on('field', (name, val, info) => {
    contactInfo[name] = val;
  });
  // done with all form fields
  bb.on('close', async () => {
      const conn = await datastore.open()
      // insert one record in the contact collection
      const contact = await conn.insertOne('contact', contactInfo);
      // count total number on contact
      const countres = await conn.getMany('contact', {query: {}, hints: {count: true}}).toArray();
      res.render('thanks', {title: "Contact us page - thank you", contact, count: countres[0].count, root, space})
  });
  // send request to Busboy for parsing
  req.pipe(bb);
})
Enter fullscreen mode Exit fullscreen mode

In this code snippet, the server extracts the form data, connects to the database, and inserts the data into a collection named 'contacts'. After successfully saving the data, the server redirects the user to a thank you page.

A screenshot of the contact upload result page is shown below.

Form upload result

Codehooks Studio

You can use the Studio to access your database at any time. This is a screenshot of the Studio after submitting to the contact page with some test data.

Codehooks Studio

Deploying your app

Deploying your application on Codehooks.io involves uploading resources and executing the deployment script defined in the package.json file.

npm run upload
Enter fullscreen mode Exit fullscreen mode

Lets take a closer look at what the deploy script is actually doing. In the package.json file we can see that the deploy command consists of 3 other steps.

"scripts": {
    "deploy": "coho upload --src './assets'  && coho upload --src './views' && coho deploy --upload"
  }
Enter fullscreen mode Exit fullscreen mode

The deploy snippet from the package.json file.

Breakdown of the deploy command

  • coho upload --src './assets' - upload all files in the assets folder, e.g. images, css and client side JavaScripts
  • coho upload --src './views' - upload all Handlebars page templates
  • coho deploy --upload - compile and deploy the index.js serverless application, and upload the source code to share with team members

Use this script to upload all assets, pages and deploy your application everytime something has changed.

Each time you change something in your app, just run npm run upload - it's that simple!

Summary and Conclusion

This blog post has provided a working example for the development of dynamic web pages using codehooks.io's serverless JavaScript, Handlebars, Tailwind CSS, and DaisyUI. We covered the essential steps: setting up the serverless environment, implementing efficient templating with Handlebars, configuring static assets, and handling form submissions with database integration.

In conclusion, this approach demonstrates that creating dynamic web applications need not always involve complex SPA frameworks. By leveraging serverless architectures and traditional templating methods, developers can achieve efficient, accessible, and rapid web development. This method is particularly beneficial for projects where simplicity, speed, and ease of use comes first.

Links to Resources and Live Example

💖 💪 🙅 🚩
restdbjones
Jbee - codehooks.io

Posted on December 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related