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.
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
Download a copy of the example code from Github.
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)
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.
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.
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:
// 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.
<!-- 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.
{{! 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.
// 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.
// 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:
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.
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>
A screenshot of the product details page is shown below.
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.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 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);
})
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.
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.
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
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"
}
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.
Top comments (0)