Skip to main content

· 8 min read

We’ll build a web app to solve every developer's most common problem – finding an excuse to justify our messy work! And will do it with a single config file that covers the full-stack app architecture plus several dozen lines of code. In the quickest possible way, so we can’t excuse ourselves from building it!

Best excuse of all time

Best excuse of all time! Taken from here.

The requirements were unclear.#

We’ll use Michele Gerarduzzi’s open-source project. It provides a simple API and a solid number of predefined excuses. A perfect fit for our needs. Let’s define the requirements for the project:

  • The app should be able to pull excuses data from a public API.
  • Save the ones you liked (and your boss doesn't) to the database for future reference.
  • Building an app shouldn’t take more than 15 minutes.
  • Use modern web dev technologies (NodeJS + React)

As a result – we’ll get a simple and fun pet project. You can find the complete codebase here.

Final result

There’s an issue with the third party library.#

Setting up a backbone for the project is the most frustrating part of building any application.

We are installing dependencies, tying up the back-end and front-end, setting up a database, managing connection strings, and so on. Avoiding this part will save us a ton of time and effort. So let’s find ourselves an excuse to skip the initial project setup.

Ideally – use a framework that will create a project infrastructure quickly with the best defaults so that we’ll focus on the business logic. A perfect candidate is Wasp. It’s an open-source, declarative DSL for building web apps in React and Node.js with no boilerplate

How it works: developer starts from a single config file that specifies the app architecture. Routes, CRUD API, auth, and so on. Then adds React/Node.js code for the specific business logic. Behind the scenes, Wasp compiler will produce the entire source code of the app - back-end, front-end, deployment template, database migrations and everything else you’ve used to have in any other full-stack app.

Wasp architecture

So let’s jump right in.

Maybe something's wrong with the environment.#

Wasp intentionally works with the LTS Node.js version since it guarantees stability and active maintenance. As for now, it’s Node 16 and NPM 8. If you need another Node version for some other project – there’s a possibility to use NVM to manage multiple Node versions on your computer at the same time.

Installing Wasp on Linux (for Mac/Windows, please check the docs):

curl -sSL https://get.wasp-lang.dev/installer.sh | sh

Now let’s create a new web app named ItWaspsOnMyMachine.

wasp new ItWaspsOnMyMachine

Changing the working directory:

cd ItWaspsOnMyMachine

Starting the app:

wasp start

Now your default browser should open up with a simple predefined text message. That’s it! 🥳 We’ve built and run a NodeJS + React application. And for now – the codebase consists of only two files! main.wasp is the config file that defines the application’s functionality. And MainPage.js is the front-end.

Initial page

That worked perfectly when I developed it.#

1) Let’s add some additional configuration to our main.wasp file. So it will look like this:

main.wasp | Defining Excuse entity, queries and action

// Main declaration, defines a new web app.app ItWaspsOnMyMachine {
  // Used as a browser tab title.                                    title: "It Wasps On My Machine",
  head: [    // Adding Tailwind to make our UI prettier    "<script src='https://cdn.tailwindcss.com'></script>"  ],
  dependencies: [     // Adding Axios for making HTTP requests                                              ("axios", "^0.21.1")  ]}
// Render page MainPage on url `/` (default url).route RootRoute { path: "/", to: MainPage }                 
// ReactJS implementation of our page located in `ext/MainPage.js` as a default exportpage MainPage {                                               component: import Main from "@ext/MainPage.js"}
// Prisma database entityentity Excuse {=psl                                              id          Int     @id @default(autoincrement())    text        Stringpsl=}
// Query declaration to get a new excusequery getExcuse {                                             fn: import { getExcuse } from "@ext/queries.js",  entities: [Excuse]}
// Query declaration to get all excusesquery getAllSavedExcuses {                                    fn: import { getAllSavedExcuses } from "@ext/queries.js",  entities: [Excuse]}
// Action to save current excuseaction saveExcuse {                                           fn: import { saveExcuse } from "@ext/actions.js",  entities: [Excuse]}

We’ve added Tailwind to make our UI more pretty and Axios for making API requests.

Also, we’ve declared a database entity called Excuse, queries, and action. The Excuse entity consists of the entity’s ID and the text.

Queries are here when we need to fetch/read something, while actions are here when we need to change/update data. Both query and action declaration consists of two lines – a reference to the file that contains implementation and a data model to operate on. You can find more info in the docs. So let’s proceed with queries/actions.

2) Create two files: “actions.js” and “queries.js” in the ext folder.

.../ext/actions.js | Defining an action
export const saveExcuse = async (excuse, context) => {  return context.entities.Excuse.create({    data: { text: excuse.text }  })}
.../ext/queries.js | Defining queries
import axios from 'axios';
export const getExcuse = async () => {  return axios    .get('https://api.devexcus.es/')    .then(res => {      return res.data;    })    .catch(error => {      console.error(error);    });}
export const getAllSavedExcuses = async (_args, context) => {  return context.entities.Excuse.findMany()}

Let’s add saveExcuse() action to our actions.js file. This action will save the text of our excuse to the database. Then let’s create two queries in the queries.js file. First, one getExcuse will call an external API and fetch a new excuse. The second one, named getAllSavedExcuses, will pull all the excuses we’ve saved to our database.

That’s it! We finished our back-end. 🎉 Now, let’s use those queries/actions on our UI.

3) Let’s erase everything we had in the MainPage.js file and substitute it with our new UI.

.../ext/MainPage.js | Updating the UI
import React, { useState } from 'react'import { useQuery } from '@wasp/queries'import getExcuse from '@wasp/queries/getExcuse'import getAllSavedExcuses from '@wasp/queries/getAllSavedExcuses'import saveExcuse from '@wasp/actions/saveExcuse'
const MainPage = () => {  const [currentExcuse, setCurrentExcuse] = useState({ text: "" })  const { data: excuses } = useQuery(getAllSavedExcuses)
  const handleGetExcuse = async () => {    try {      setCurrentExcuse(await getExcuse())    } catch (err) {      window.alert('Error while getting the excuse: ' + err.message)    }  }
  const handleSaveExcuse = async () => {    if (currentExcuse.text) {      try {        await saveExcuse(currentExcuse)      } catch (err) {        window.alert('Error while saving the excuse: ' + err.message)      }    }  }
  return (    <div className="grid grid-cols-2 text-3xl">      <div>          <button onClick={handleGetExcuse} className="mx-2 my-1 p-2 bg-blue-600 hover:bg-blue-400 text-white rounded"> Get excuse </button>          <button onClick={handleSaveExcuse} className="mx-2 my-1 p-2 bg-blue-600 hover:bg-blue-400 text-white rounded"> Save excuse </button>        <Excuse excuse={currentExcuse} />      </div>      <div>        <div className="px-6 py-2 bg-blue-600 text-white"> Saved excuses: </div>        {excuses && <ExcuseList excuses={excuses} />}      </div>    </div>  )}
const ExcuseList = (props) => {   return props.excuses?.length ?  props.excuses.map((excuse, idx) => <Excuse excuse={excuse} key={idx} />) : 'No saved excuses'}
const Excuse = ({ excuse }) => {  return (    <div className="px-6 py-2">      {excuse.text}    </div>  )}
export default MainPage

Our page consists of three components. MainPage, ExcuseList and Excuse. It may seem at first that this file is pretty complex. It’s not, so let’s look a bit closer.

Excuse is just a div with an excuse text, ExcuseList checks if there are any excuses. If the list is empty – show a message No saved excuses. In other case – excuses will be displayed.

MainPage contains info about the current excuses and the list of already saved excuses. Two buttons click handlers handleGetExcuse and handleSaveExcuse. Plus, the markup itself with some Tailwind flavor.

4) Before starting an app – we need to execute database migration because we changed the DB schema by adding new entities. If you’ve had something running in the terminal – stop it and run:

wasp db migrate-dev

You’ll be prompted to enter a name for the migration. Something like init will be ok. Now we can start the application!

wasp start

Final empty result

Now you can click the “Get excuse” button to receive an excuse. And save the ones you like into the DB with the “Save excuse” button. Our final project should look like this:

Final result

It would have taken twice as long to build it properly.#

Now we can think of some additional improvements. For example:

  • 1) Add a unique constraint to Entity’s ID so we won’t be able to save duplicated excuses.
  • 2) Add exceptions and edge cases handling.
  • 3) Make the markup prettier.
  • 4) Optimize and polish the code

So, we’ve been able to build a full-stack application with a database and external API call in a couple of minutes. And now we have a box full of excuses for all our development needs.

Box of excuses for the win!

We are in Alpha (try it out)!Join our communityWork with us

· 7 min read
Martin Sosic

Haskell is a unique and beautiful language that is worth learning, if for nothing else, then just for the concepts it introduces. They will expand your view on programming.

I have been programming in Haskell on and off since 2011 and professionally for the past 2 years, building a compiler. While in that time Haskell has become much more beginner-friendly, I keep seeing beginners who are overwhelmed by numerous popular options for build tools, installers, introductory educational resources, and similar. Haskell’s homepage getting a call from the previous decade to give them their UX back also doesn’t help.

That is why I decided to write this opinionated and practical post that will tell you exactly how to get started with Haskell in 2022 in the most standard / common way. Instead of worrying about decisions that you are not equipped to make at the moment (like “what is the best build tool Ifd??”), you can focus on enjoying learning Haskell :)!

· 8 min read
Shayne Czyzewski

I have been programming professionally for over a decade, using a variety of languages day-to-day including Ada, C, Java, Ruby, Elixir, and JavaScript. I’ve also tried some obscure ones, albeit less frequently and for different purposes: MIPS assembly language and OCaml for academic work (I’m a BS, MS, and PhD dropout in CS), and Zig for some side projects. In short, I like learning new languages (at least at a surface level) and have been exposed to different programming paradigms, including functional.

Yet, I have never done Haskell. I’ve wanted to learn it since my college days, but never got the time. In late 2021, though, my curiosity took over. I wanted to see for myself if the mystique and the Kool-Aid hype (or hate) around it are justified. :P So, I decided I’d start learning it on the side and also look for a company that uses it as my next gig. That’s how my Haskell journey started, and how I got into Wasp a few months later.

· 31 min read
Vasili Shynkarenka

Except for a handful of companies who send people to Mars or develop AGI, most startups don’t seem to offer a good reason to join them. You go to their websites and all you see is vague, baseless, overly generic mission-schmission/values-schvalues HR nonsense that supposedly should turn you into a raving fan of whatever they’re doing and make you hit that “Join” button until their servers crash. Well…

Some people think that’s because most startups aren’t worth joining. I disagree. This argument generalizes one’s own reasons for joining a startup onto every other human being out there, which is unlikely to be true. I think most startups, no matter how ordinary, do have a reason to join them; a good reason; even many good reasons — they just fail to communicate them well. They’re like a shy nerd on Tinder with an empty bio and no profile pic: a kind, intelligent, and thoughtful human being who, unfortunately, will be ruthlessly swiped left — not because he’s a bad match but because his profile doesn’t show why he’s a good one.

Visually, this “Tinder profile problem” looks like this:

· 11 min read
Matija Sosic

We are working on a config language / DSL for building web apps that integrates with React & Node.js. A number of times we've been asked “Why are you bothering creating a new language for web app development? Isn’t Github Copilot* soon going to be generating all the code for developers anyhow?”.

This is on our take on the situation and what we think things might look like in the future.

· 4 min read

Wasp app deploye to Gitpod

"All good thoughts and ideas mean nothing without the proper tools to achieve them."
Jason Statham

TL;DR: Wasp allows you to build and deploy a full-stack JS web app with a single config file. Gitpod spins up fresh, automated developer environments in the cloud, in seconds. A perfect tandem to win a hackathon and enjoy free pizza even before other teams even started to set up their coding env and realized they need to update their node version.