Back to the Future: Exploring Frameworkless and Serverless Web Development
Tags: serverless, frontend, webdev, database
Published: 2023-12-11 18:01:17

## 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](https://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](https://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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0o2g8574a9dhc9adk93.png)

![About page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iu0h967rfjhx824xqx5z.png)


![Mobile view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55gsxoimbezd70575sfs.png)

Link to a live example: [https://youthful-levy-7b1f.codehooks.io](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.

```shell
npm i -g codehooks
```

Download a copy of the example code from Github.

```shell
git clone https://github.com/RestDB/codehooks-io-examples.git
```

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

```shell
cd codehooks-io-examples/dynamicweb
```

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.

```shell
coho init --empty
```
_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.

```shell
npm install
```

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
```

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

```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();
```
*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.

```handlebars
<!-- layout.hbs: Master layout template -->
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
    <!-- Head elements -->
</head>
<body>
    {{{body}}}
    <!-- Common elements -->
</body>
</html>
```
*The master layout defines the basic HTML structure for all pages.*

```handlebars
{{! products.hbs: Extending the master layout }}
{{#extend "layout"}}
    {{#content "main"}}
    <h1>Our Products</h1>
    <!-- Product-specific content -->
    {{/content}}
{{/extend}}
```
*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.

```javascript
// 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...
```
*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.

```javascript
// 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})
})
```
*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:

```shell
coho import -f exampledata/products.json -c products
```
_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzezp4te761cs1edc1i4.png)

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`.

```hbs
<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>
```

A screenshot of the product details page is shown below.


![Product detail page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s7fyirkpiecbg11jq2k.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejqxmh4rwcvfeds7bubi.png)

```handlebars
{{! 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}}
```

_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](https://www.npmjs.com/package/busboy) and saving each contact data payload to a collection in the database.

```javascript
// 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);
})
```
_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uougseirlo7nkidhk2p5.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8xscbs0y5zea77qz1kvb.png)

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

```shell
npm run upload
```
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.

```json
"scripts": {
    "deploy": "coho upload --src './assets'  && coho upload --src './views' && coho deploy --upload"
  }
```
_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
- [Complete Source Code on GitHub](https://github.com/RestDB/codehooks-io-examples/tree/main/dynamicweb)
- [Live Example of the Application](https://dynamicweb-h2pl.api.codehooks.io/dev)
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
- [DaisyUI Components](https://daisyui.com/components/)
- [Handlebars Documentation](https://handlebarsjs.com/guide/)
- [Busboy](https://www.npmjs.com/package/busboy)
- [Codehooks.io Documentation](https://codehooks.io/docs)