< Develop a Frontend on Your Local Machine
Skip to "Serve the Frontend from a Function"
Download assets and get started
$ cd frontend
$ npm install
$ npm run start
- Setup Your Binaris Environment
- Create a Binaris Function to Serve a Web App
- Deploy the React Project with Your Function
For the next section you will need a Binaris account, if you already have one skip the following four steps.
- Visit signup
- Follow the instructions and create your new Binaris account
- Install the CLI via
npm
npm install binaris -g
- Use
bn login
to authenticate with your newly created Binaris account - (Optional) visit our getting started page to learn the basics
Make a new sub-directory inside frontend
(generated with create-react-app
), name it serve_todo
.
$ ls
README.md package.json serve_todo yarn.lock
node_modules public src
$ mkdir serve_todo
Navigate into the serve_todo
directory and use bn create
to generate the template files for our serving function.
$ cd serve_todo
$ bn create node8 public_serve_todo
Created function public_serve_todo in /home/ubuntu/todo/frontend/serve_todo
(use "bn deploy public_serve_todo" to deploy the function)
The public_
prefix is essential because it tells the Binaris backend that the function should be publicly available to the world. This allows us to utilize the function just as we would any other webserver via https. Keep in mind that although public_
is the right choice here, there are many other cases where requiring authentication and keeping things private is preferred.
Before we start writing code, it's important to define our goal and what we need to achieve that goal. We know our goal is to serve our React app via a function, which means we need a way to serve static files directly from the function itself.
Now, it's time to write our first Binaris function together.
-
Strip the automatically generated contents from the handler body in
function.js
> function.js --- exports.handler = async (body, context) => { - const name = context.request.query.name || body.name || 'World'; - return `Hello ${name}!`; }
-
Create & define the current resource path as the path provided in the input request.
> function.js --- exports.handler = async (body, context) => { + let resourcePath = context.request.path; }
-
The above code will work when passed an explicit resource path, we also want to handle the case where only the base URL is provided. In that case, we simply want to return the
index.html
.> function.js --- let resourcePath = context.request.path; +if (resourcePath === '/' || resourcePath === undefined) { + resourcePath = '/index.html'; +}
-
Now that we know the path to our resource, we can load the requested content. We assume that our assets will be deployed with the function and therefore should be available on the local filesystem. The Node
fs
module seems like a great fit here, but unfortunately it's not nativelyasync
. To remedy this, we'll rely on the package mz which provides a promisified version of the nativefs
module.> function.js --- if (resourcePath === '/' || resourcePath === undefined) { resourcePath = '/index.html'; } +// we assume that all paths provided have a leading "/" +const webResource = await fs.readFile(`.${resourcePath}`);
We also need to add our
require
statement formz
at the top of the file.> function.js --- +const fs = require('mz/fs'); + exports.handler = async (body, context) => {
-
Now that we have the requested resource loaded, we need to determine what type of resource it is. That way, the correct
Content-Type
header can be set. Luckily, the npm modulemime-types
, in combination with the builtin Node modulepath
, can do the heavy lifing for us.> function.js --- } const webResource = await fs.readFile(`.${resourcePath}`); +const resourceType = mime.contentType(path.extname(resourcePath));
Once again, we need to add
require
statements formime-types
andpath
at the top of the file.> function.js --- const fs = require('mz/fs'); +const mime = require('mime-types'); +const path = require('path');
-
All that's left is returning a
HTTPResponse
object and filling it in with the variables we created.> function.js --- const webResource = await fs.readFile(`.${resourcePath}`); const resourceType = mime.contentType(path.extname(resourcePath)); +return new context.HTTPResponse({ + statusCode: 200, + headers: { + 'Content-Type': resourceType, + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept', + }, + body: webResource, +});
Note:
'Access-Control-Allow-Origin': '*'
means that anyone (even evil websites) will be able to modify your todo list. Consider using your own domain (or function URL) to alleviate this issue.HTTPResponse breakdown
We consider returning any resource a success and therefore "200"
> function.js --- +statusCode: 200,
The first header,
'Content-Type'
, identifies the type of our response body. For its value, we can simply use theresourceType
variable that was calculated using themime-types
package.React has issues when you use non-root routes as a homepage. To alleviate this, we will enable CORS in our response by adding the
'Access-Control-Allow-Origin'
and'Access-Control-Allow-Headers'
headers.> function.js --- +headers: { + 'Content-Type': resourceType, + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept', +},
Last but not least we need to actually provide the content that should be returned in the response. The
webResource
variable contains the resource we loaded from file, and therefore should go into the body field.> function.js --- +body: webResource,
Final state of function.js
const fs = require('mz/fs');
const mime = require('mime-types');
const path = require('path');
exports.handler = async (body, context) => {
let resourcePath = context.request.path;
if (resourcePath === '/' || resourcePath === undefined) {
resourcePath = '/index.html';
}
const webResource = await fs.readFile(`.${resourcePath}`);
const resourceType = mime.contentType(path.extname(resourcePath));
return new context.HTTPResponse({
statusCode: 200,
headers: {
'Content-Type': resourceType,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
},
body: webResource,
});
};
Before We Forget
We do have one final task before moving on. Although we required our dependencies, they haven't been installed yet. To install them run
$ npm init -y
$ npm install mime-types mz path
inside of the serve_todo
directory.
Before we can see our function in action, there are two small changes we need to make in our outer frontend/package.json
. These changes will allow our React app to be hosted in the recently created public_serve_todo
function.
-
Add a "homepage" so that React routing uses your account specific function URL. Make sure to replace
<ACCOUNT_ID>
with your specific Binaris account ID. Assuming you successfully ranbn login
, your account ID can be found in~/.binaris.yml
.Note: Your Account ID will always be a unique number, 10 digits in length.
> frontend/package.json --- "private": true, +"homepage": "https://run.binaris.com/v2/run/<ACCOUNT_ID>/public_serve_todo", "dependencies": {
-
Add a new script to
frontend/package.json
which will save some time when deploying our function> frontend/package.json --- "scripts": { "start": "react-scripts start", "build": "react-scripts build", + "deploy": "cp -R serve_todo/* build/ && cd build && bn deploy public_serve_todo", "test": "react-scripts test", "eject": "react-scripts eject"
Finally, deploy your function using npm run build
followed by npm run deploy
and vist the function URL (printed by your console) in the browser. Hurray!
$ pwd
/Users/ubuntu/todo/frontend
$ npm run build
$ npm run deploy