Defining and Submitting Forms
Learning objectives
- You understand how forms work and can create forms using HTML.
- You can read submitted form data on the server.
HTML forms
HTML Forms are used for entering data and sending data to the server. Forms are created using the HTML form
-element. Each form can contain from zero to a nearly unlimited amount of form fields. Each field is represented with a separate element.
Form element
The form element form
has two attributes: method
and action
. The method describes the used HTTP method (either POST or GET), while the action describes the path to which the data is sent. If the method
attribute is missing, GET
is used by default. Similarly, if the action
attribute is missing, the current address and path is used by default.
The example below outlines a form element that uses the POST
method and sends data to the path /addresses
.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
</form>
</body>
</html>
The form element is empty and it does not contain any input fields or any way to submit the form. Let's next look into how to add an input fields to the form and provide the means to submit the form.
A form with one input field
Input elements are created using the input
element. The input
element has a number of attributes, of which the most important ones are type
, id
, and name
. The type
attribute describes the type of the input field, while the id
and name
attributes are used for identifying the input field. The id
attribute is used for referring to the input field from other elements, while the name
attribute is used for identifying the input field when the form is submitted.
The
name
attribute of the input fields is used for identifying input fields in submitted form data.
The example below creates a form that contains an input field for entering text (the value of the type
attribute is text
). The name of the input field is address
. The form also has an input element for submitting the form. The input element has the type submit
, which creates a button -- the value
attribute is used as the text for the button.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
<label for="address">Address:</label>
<input type="text" id="address" name="address" /><br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
When the button is pressed, the browser sends -- submits -- the data using a HTTP POST request to the path /addresses
on the server, as indicated by the method- and action attributes of the form element. The following example shows the form. Note that in this material, if you submit the form, you will see an empty page (we'll look into functionality for handling form data later).
Question not found or loading of the question is still in progress.
Form labels
The label
element is used for referring to an input field within the form and for adding a textual description for the input field. The attribute for
of the label
element tells which input field the label refers to. The value for the attribute for
should be the same as the value of the id
attribute of the input field that the label refers to.
The example below shows a form that contains a label for the input field. The label has the text "Address: " and it refers to the input field with the id
attribute address
.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
<label for="address">Address:</label>
<input type="text" id="address" name="address" /><br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
If we would wish that the label for the address field is "Enter address here", we would modify the label as follows.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
<label for="address">Enter address here</label>
<input type="text" id="address" name="address" /><br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
Multiple input fields
Forms may have an unlimited amount of input fields. In the example below, there is a checkbox that provides an opportunity for indicating whether the user submitting the form enters into a raffle.
The form also contains the <br/>
element, which is used for creating line breaks.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/raffle">
<label for="address">Address:</label>
<input type="text" id="address" name="address" />
<br/>
<label for="raffle">I participate in the raffle:</label>
<input type="checkbox" id="raffle" name="raffle" />
<br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
As we observed previously, a form can contain multiple input fields. The type of the input field can be controlled using the type
attribute. There are over 20 different input types, some of which are shown in the next example.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/form-data">
<label for="email">Email:</label>
<input type="email" id="email" name="email" />
<br/>
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
<br/>
<label for="date">Date:</label>
<input type="date" id="date" name="date" />
<br/>
<label for="number">Number:</label>
<input type="number" id="number" name="number" />
<br/>
<label for="url">URL:</label>
<input type="url" id="url" name="url" />
<br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
Some input fields types such as number
and email
may do data validation on the browser, depending on the browser. As data validation done in the browser cannot -- and should not -- be trusted, we will look into server-side validation later on in the course.
Reading form data on server
Next, we look into how data sent using a form can be read on the server. First, we serve a form from the server. Then, to read the form data, we will use Hono's asynchronous parseBody
method, which is a part of the req
attribute of the context. The parseBody
method returns a promise that resolves into an object that contains the form data.
Serving a form from the server
Let's first create an application with a form that has an input field for entering an address. The address is sent to the path /addresses
on the server server using a POST
request. Save the following HTML content to an index.eta
file in a templates
-folder.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
<label for="address">Address:</label>
<input type="text" id="address" name="address" /><br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
Further, add the following JavaScript code to a app.js
file.
import { Eta } from "https://deno.land/x/eta@v3.4.0/src/index.ts";
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
const eta = new Eta({ views: `${Deno.cwd()}/templates/` });
const app = new Hono();
app.get("/", (c) => c.html(eta.render("index.eta")));
Deno.serve(app.fetch);
When we run the application and make a request to it, we see the following response.
curl localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
<label for="address">Address:</label>
<input type="text" id="address" name="address" /><br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>%
If you are unsure why the form is received as a response from the server, revisit the chapters Hono Web Framework and View Templates.
Reading form data
As the form is sent using a POST
request to the path /addresses
, let's implement functionality to read the form data from the request for that method and path. To read form data in Hono, we use the asynchronous parseBody
method that is provided by the req
attribute of the context. The parseBody
method returns a promise that resolves into an object that contains the form data.
Let's first implement a route that returns the text OK
and logs the address
-field of the form data to the console. The example below shows the implementation of the route.
import { Eta } from "https://deno.land/x/eta@v3.4.0/src/index.ts";
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
const eta = new Eta({ views: `${Deno.cwd()}/templates/` });
const app = new Hono();
app.get("/", (c) => c.html(eta.render("index.eta")));
app.post("/addresses", async (c) => {
const body = await c.req.parseBody();
console.log(body);
return c.text("OK");
});
Deno.serve(app.fetch);
When we run the application and submit the form (using "My Form Address" as the input), we see the following output in the console.
deno run --allow-net --allow-read --unstable --watch app-form.js
Watcher Process started.
Listening on http://localhost:8000/
{ address: "My Form Address" }
Similarly, in the browser where we sent the form, we see the text "OK".
Two input elements
Let's try the same, but with two input elements. The form below contains two input fields, one for name and one for address. Save the form into the index.eta
file.
<!DOCTYPE html>
<html>
<head>
<title>Hello forms!</title>
</head>
<body>
<form method="POST" action="/addresses">
<label for="name">Name:</label>
<input type="text" id="name" name="name" /><br/>
<label for="address">Address:</label>
<input type="text" id="address" name="address" /><br/>
<input type="submit" value="Submit form" />
</form>
</body>
</html>
Open the browser again at the address localhost:8000
and submit the form. Below, we've entered "My Form Name" and "My Form Address" as the address.
deno run --allow-net --allow-read --unstable --watch app-form.js
Watcher Process started.
Listening on http://localhost:8000/
{ name: "My Form Name", address: "My Form Address" }
As the form data is parsed into a JavaScript object, we could as well log the name and address separately.
import { Eta } from "https://deno.land/x/eta@v3.4.0/src/index.ts";
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
const eta = new Eta({ views: `${Deno.cwd()}/templates/` });
const app = new Hono();
app.get("/", (c) => c.html(eta.render("index.eta")));
app.post("/addresses", async (c) => {
const body = await c.req.parseBody();
console.log(`Name: ${body.name}`);
console.log(`Address: ${body.address}`)
return c.text("OK");
});
Deno.serve(app.fetch);
Now, with the same inputs, the console output is as follows.
deno run --allow-net --allow-read --unstable --watch app-form.js
Watcher Process started.
Listening on http://localhost:8000/
Name: My Form Name
Address: My Form Address
Undefined and Promise { <pending> }
Try modifying the application so that the function responsible for handling the form submission does not use the async
and await
keywords. In other words, modify the route to match the following.
app.post("/addresses", (c) => {
const body = c.req.parseBody();
console.log(`Name: ${body.name}`);
console.log(`Address: ${body.address}`)
return c.text("OK");
});
When you post form data, you notice in the console that both name and address are undefined. This stems from the request body being a promise that resolves into the actual form data. As the promise is not awaited, the form data is not available when the console logs are executed. Modify the route to match the following, and you'll see that the output is a promise.
app.post("/addresses", (c) => {
const body = c.req.parseBody();
console.log(body);
return c.text("OK");
});