DEV Community

Thomas Hunter II
Thomas Hunter II

Posted on • Edited on

Hosting a Static Site and Contact Form with Osgood

Our newly released project, Osgood, is a tool for building HTTP applications that run JavaScript on the server. It has an emphasis on security and by default enforces isolation between the different routes.

One of the features we added is the ability to serve static content. In this post we'll show how easy it is to build a website which is almost entirely static but does require some server-side logic, in this case a contact form.

This application will make use of Mailgun to send email. Note that we're using the demo Mailgun API key and a demo URL—if you want to adapt this application for your own use you'll need to create an account and use real credentials.

Application File (app.js)

This file handles global configuration of Osgood, how to route requests, and the policies of the different routes. Every application requires an application file.

#!/usr/bin/env osgood

app.port = 3000;

app.route('POST', '/contact', 'contact.js', policy => {
  // Mailgun demo URL
  policy.outboundHttp.allowPost('https://api.mailgun.net/v3/samples.mailgun.org/messages');
});

// GET requests beginning with / will map to the ./static directory
app.static('/', './static');

This file specifies two different routes. The first one is rather specific and captures POST requests made to the /contact endpoint. When these requests are made it will trigger the contact.js worker file. Based on the policy configuration that route will only be allowed to make a POST request to the specified Mailgun endpoint. Any other attempts at making I/O requests, such as making a GET request to the GitHub API, would fail.

The other route is for static content. Any other request (i.e. GET requests beginning with /, aka not POST /contact) will translate into a lookup in the ./static directory. Osgood defaults to serving a file named index.html when a request to a directory has been made.

Worker File (contact.js)

This worker file will handle the only route defined by our application which isn't related to serving static content.

Osgood workers work by exporting a default function, usually with the async keyword. The value returned from this function will be used to determine the response for this particular HTTP request.

This function can essentially be thought of as a controller for your application. Within this function we perform a lot of validation logic. Once we're satisfied we generate an instance of the FormData class. Once the instance has the necessary values attached we can send the data along to Mailgun by making a fetch request. (The APIs available should look pretty familiar if you're used to writing JavaScript that runs in a web browser.)

Finally, once the request has been sent, we notify the user that their email has been successfully delivered.

export default async (request) => {
  try {
    var req = await request.json();
  } catch (e) {
    return json({error: 'CANNOT_PARSE', message: "Invalid JSON Payload Provided"}, 400);
  }

  const email = new FormData();

  if (!('email' in req) || !('name' in req) || !('message' in req)) {
    return json({error: 'MISSING_FIELDS', message: "Invalid JSON Payload Provided"}, 400);
  } else if (typeof req.email !== 'string' || typeof req.name !== 'string' || typeof req.message !== 'string') {
    return json({error: 'INVALID_FIELD_TYPES', message: "Invalid JSON Payload Provided"}, 400);
  } else if (!req.name || !req.email || !req.message) {
    return json({error: 'EMPTY_VALUE', message: "Please supply all fields"}, 400);
  }

  email.append('from', `${req.name} <${req.email}>`);
  email.append('to', 'spam@intrinsic.com');
  email.append('subject', "Contact form email");
  email.append('text', req.message);

  try {
    // Mailgun demo URL
    await fetch('https://api.mailgun.net/v3/samples.mailgun.org/messages', {
      method: 'POST',
      headers: new Headers({
        // Mailgun demo key
        Authorization: 'Basic ' + btoa('api:key-3ax6xnjp29jd6fds4gc373sgvjxteol0')
      }),
      body: email
    });
  } catch (e) {
    return json({error: 'CANNOT_SEND', message: "Cannot parse provided JSON"}, 500);
  }

  return json({success: true, message: "Email has been sent"});
}

// helper for setting status code
function json(obj, status = 200) {
  const headers = new Headers({
    'Content-Type': 'application/json'
  });

  const body = JSON.stringify(obj);

  const response = new Response(body, { headers, status });

  return response;
}

Running the Project

Our project has the following hierarchy of files to support this application:

  • static/
    • scripts/
    • contact.js
    • styles/
    • main.css
    • index.html
  • app.js
  • contact.js

We then run the application by executing the following command:

$ osgood app.js

After that the application can be seen by visiting http://localhost:3000.

The files used in this example can be downloaded here: osgood/examples/contact

You'll also need to install a recent Osgood Release to run the example.


Checkout the next post in this series, Osgood and CouchDB.

Top comments (0)