In this tutorial, we’ll introduce Moon.js by understanding what it is and why it was created. We’ll then put that knowledge to use by building a blogging application, which will use the JSONPlaceholder API.
Moon.js is a tiny (2KB), functional, declarative, minimal, fast library for functional user interfaces. It is based on the idea that modern applications are complex and that they very likely require third-party libraries for any effects other than updating a view.
To address this, Moon is based on pure functions, which can take input from and return output to drivers. Drivers are responsible for views, state, HTTP requests, routing, etc.
Moon comes with a bunch of features out of the box:
localStorage
to persist data in the browserIn this tutorial, we’ll be working with the HTTP driver for fetching API using the JSONPlaceholder API.
We can install Moon using the CLI:
npm install -g moon-cli
The above command will install moon globally on our system. More information on installing the package globally can be found on the npm documentation.
Once the above step is complete, we can bootstrap a Moon project with the following command:
moon create introduction-to-moon.js
The above command will create the following directory structure:
Now, we can run the following command to install all the required dependencies and start the application:
cd introduction-to-moon.js && npm install && npm run dev
At this point, our application should be up and running on http://localhost:8080/
:
Moon gives us two scripts by default:
dev
– this command will start a webpack server with live reloadbuild
– this command will generate a production build of our applicationNext, we’ll add Tailwind CSS to our application to provide some default styles. We can do so by adding the following line to our src/index.html
file:
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
Note: This is not the recommended method of installing Tailwind CSS. More options are provided in the Tailwind CSS documentation.
Next, we’ll be adding the feature with which we’ll be able to fetch data from an API like JSONPlaceholder and show it on the browser. This API will be paginated, so we’ll fetch only the first post initially, and then, on clicking a button, we’ll fetch the subsequent posts.
JSONPlaceholder is a very easy API to use. It’s free and can be fetched using the code below:
fetch('https://jsonplaceholder.typicode.com/posts/1') .then(response => response.json()) .then(json => console.log(json))
This will return a JSON response like the following:
{ userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto" }
To get started with our application, let’s first define the HTML structure of the layout. We can create a new file, src/view/home/index.js
, with the following content:
// src/view/home/index.js import Moon from "moon"; import navbar from "../components/navbar"; import post from "../components/post"; const { section, div } = Moon.view.m; export default ({ data }) => { return ( <div> <section> <navbar /> </section> <section> <post data=data /> </section> </div> ); };
We’ll be creating the navbar
and post
components next. The navbar
component is responsible for showing a dummy navigation bar. We can create a new file, src/view/components/navbar/index.js
, with the following content:
// src/view/components/navbar/index.js import Moon from "moon"; const { div, ul, li, a } = Moon.view.m; export default () => { return ( <div className="bg-gray-100 border-b"> <div className="max-w-2xl mx-auto p-8 flex justify-between"> <div> <ul class="flex"> <li class="mr-6 font-bold"> <a class="text-blue-500 hover:text-blue-800" href="#"> Home </a> </li> </ul> </div> <div> <ul class="flex"> <li class="mr-6 font-bold"> <a class="text-blue-500 hover:text-blue-800" href="#"> About </a> </li> <li class="mr-6 font-bold"> <a class="text-blue-500 hover:text-blue-800" href="#"> Contact </a> </li> </ul> </div> </div> </div> ); };
Next, we’ll create our post
component. The post
component is responsible for showing the posts fetched from the API, along with a button to fetch the next post from the API and render it on the browser. So let’s create a new file src/view/components/post/index.js
with the following content:
// src/view/components/post/index.js import Moon from "moon"; import home from "../../home"; const { div, h2, p, button } = Moon.view.m; const post = ({ data }) => { return ( <div className="bg-white"> <div className="max-w-2xl mx-auto p-8"> <div className="mb-8"> <h2 className="font-bold text-xl mb-2">{data.title}</h2> <p className="font-light">{data.body}</p> </div> </div> </div> ) } export default post
This will show the post. However, let’s add a button for fetching the subsequent posts:
// src/view/components/post/index.js const post = ({ data }) => { const handleClick = () => { Moon.run(() => { const postCount = data.postCount + 1 return({ http: [ { method: "GET", url: `https://jsonplaceholder.typicode.com/posts/${postCount}`, headers: { "Content-type": "application/json" }, onLoad: ({ http }) => { const response = {...JSON.parse(http.body), postCount} return { view: <home data=response /> }; }, } ] })} ); }; return ( .... <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" @click=handleClick > Fetch post {data.postCount + 1} </button> .... ) }
If we click on the button now, the handleClick
function will be called. The handleClick
function will fetch the next post from the API and render it on the browser. When the API request is complete, the home
component will be rendered on the browser with the API response.
Since, this HTTP request is specific to how Moon works, let’s learn a little bit more about it. According to the Moon documentation, the HTTP driver accepts a list of HTTP requests as output. Each request can have a method
, url
, headers
, body
, responseType
, onLoad
, and onError
property. Only the url
property is required.
Moon.use({ http: Moon.http.driver }); Moon.run(() => ({ http: [{ method: "GET", // GET, POST, PUT, DELETE, etc. url: "https://jsonplaceholder.typicode.com/posts/1", headers: { "Content-Type": "application/json" // Any type }, body: "HTTP driver test", // If we need to send a body, we can use this. However, this data type must match the "headers" responseType: "json", onLoad: ({ http }) => { console.log(http); return {}; }, onError: ({ http }) => { console.error(http); return {}; } } ]}));
The above function will log the following in our browser’s console:
Moon’s debugger is also very helpful. If we didn’t define the HTTP driver, it would throw a helpful message on our browser’s console:
Next, we will need to update our src/main.js
file with the following content:
// src/main.js import Moon from "moon"; import home from "./view/home" // Define all the drivers that we'll need Moon.use({ data: Moon.data.driver, http: Moon.http.driver, view: Moon.view.driver("#root") }); Moon.run(() => { const postCount = 1 // Fetch the first post initially return({ http: [{ method: "GET", url: `https://jsonplaceholder.typicode.com/posts/${postCount}`, headers: { "Content-type": "application/json" }, onLoad: ({ http }) => { const data = {...JSON.parse(http.body), postCount} return { view: <home data=data /> }; }, }] })} );
The above code will render the home
component once the API request to fetch the first post is complete.
Now, if we visit http://localhost:8080/
, we should be able to see the following screen:
We can view the first post. If we click on the button, the next post will be fetched from the API and rendered on the browser:
In this tutorial, we’ve learned what Moon.js is and how we can build an application with it. The source code for the application we built in this tutorial is available on Github and is hosted on Vercel.
If you want to keep exploring Moon, check out the examples. You can also get acquainted with Moon using the Moon Playground or Guide.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.