diff --git a/web/blog/2022-08-26-how-and-why-i-got-started-with-haskell.md b/web/blog/2022-08-26-how-and-why-i-got-started-with-haskell.md new file mode 100644 index 000000000..6941803d1 --- /dev/null +++ b/web/blog/2022-08-26-how-and-why-i-got-started-with-haskell.md @@ -0,0 +1,76 @@ +--- +title: How and why I got started with Haskell +authors: [shayneczyzewski] +image: /img/filip-headshot-min.jpeg +tags: [webdev, haskell, language] +--- + +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import InBlogCta from './components/InBlogCta'; +import WaspIntro from './_wasp-intro.md'; +import ImgWithCaption from './components/ImgWithCaption' + + +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](https://wasp-lang.dev/blog/2021/12/21/shayne-intro) a few months later. + + + +## Why learn Haskell? + +Haskell seems to have an aura of superiority around it. Many niche and heavily academically-inspired languages do. These languages seem to be used by the enlightened minds and allow you to quickly write complex programs in a fraction of the time with significantly less code. Lisp is amongst these languages, too. Yet, nobody uses them for anything real — only toy projects. (While stroking their long, grey beards under a tree, ruminating on the philosophy of computer science.) At least, that’s the impression I got in college and at work. So, what makes Haskell interesting to learn, let alone want to use professionally? + +**First, it is functional as it gets.** While I have used lambdas and functional concepts like `map` in non-functional languages, the fact that these were my *only* choice was really interesting to me. After years of extensive OO usage, I’ve come to appreciate this epigram by Alan Perlis. I think it captures a mindset shift between the two paradigms: + +> “It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.” — Alan Perlis +> + +In OO, you create lots of classes with lots of methods. In functional, you have far fewer data structures (mostly list) with a lot more functions. So basically more functions to operate on fewer nouns, whereas OO is lots of nouns, each with many bespoke methods. (The first comment on [this Stack Overflow thread](https://stackoverflow.com/questions/6016271/why-is-it-better-to-have-100-functions-operate-on-one-data-structure-than-10-fun) explains it really well.) + +Besides, I liked the idea of referential transparency when writing pure functions. It means that you get the same result back every time you invoke a function, without fear of unknown side effects. (But the language does offer the flexibility to have side effects like IO, via Monads.) I also liked having only immutable data structures — they make reasoning about the system and data flow easier. There were many things like these two that I liked. The point is that thinking functionally really changes the way you structure and solve problems, so I was curious to give it a go. + +**Second, Haskell is lazy.** While there are pros and cons to this, it feels undeniably different. Most languages are strict, in that all function arguments are evaluated before invoking a function. This is required because of side effects; to have some expectations regarding the order in which things will run. Haskell does the opposite: it delays evaluation until it’s actually needed. + +One contrived yet helpful example of laziness is infinite data structures. Below, we define `fibs` as an infinite `List` of `Integer` values, by using references to *itself*! (You can find a runnable example [here](https://replit.com/@ShayneCzyzewsk1/LazyHaskellExample?v=1#Main.hs).) + +```haskell +fibs :: [Integer] +fibs = 0 : 1 : zipWith (+) fibs (tail fibs) + +take 10 fibs -- [0,1,1,2,3,5,8,13,21,34] +``` + +There’s a downside to laziness, too. It makes it harder to reason about performance and resource utilization. But the idea that you can define things in a declarative way but know that they are evaluated only when needed is a pretty eye-opening way to program. + +To sum up: Haskell is functional, lazy, and strongly statically typed. Just the trifecta that gets me out of bed in the morning! :D So, how did I go about learning it? + +## Hello Haskell! + +I started by reading the canonical Haskell newbie resource, “*[Learn You a Haskell for Great Good!](http://learnyouahaskell.com/),*” often abbreviated LYAH. It was very entertaining, and I learned a lot from it. At times, I wanted it to get to the point more quickly. Still, despite the amusing images and often lengthy examples, it provided me with a great conceptual foundation. I highly recommend it as your first read — it is a really well-written resource for beginners. + +After I was about 80% done with LYAH, I switched to a more recent but still popular book: “*[Haskell Programming from First Principles](https://haskellbook.com/)*.” I liked that it started with fundamentals and then moved to more complex topics, slowly but steadily developing my understanding. It was pretty long, though, and sometimes went too far into the weeds. It also had a tinge of intellectual flexing at certain points. Still, it was a good read. I’d read it again if I were starting over. + +I also tried [a Haskell course from Google](https://github.com/google/haskell-trainings). Despite being brief, it explains the key concepts in a relatively complete way. If videos are your thing, it might be a solid way to get up to speed. + +In short, skimming an intro book to get your foundation solid would be the best bet. I’d also recommend trying out many different online resources when covering more intermediate topics, like Monad Transformers, for example. And don’t worry if it takes a while to start feeling comfortable with things that are pretty specific to Haskell! It just takes some time, and often it is more confusing to derive/deeply understand than to just start using them at first. The understanding will come over time. (Of course, sometimes [pictures](https://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html) help!) + +## Setup and IDE support + +Getting Haskell up and running was surprisingly straightforward, even though I ran it on an M1 MacBook Air, which was considered a pretty new architecture in 2021. Since the entire toolchain was not fully ARM-compatible back then, some of the setup advice required a bit of modification. But that was no big deal: I used `ghcup`, installed HLS in VS Code, and bam! — I had Haskell up and running. It was a pretty nice experience. + +Some minor downsides I recall: + +- There doesn’t seem to be a consensus on which build and package management tool to use, Cabal or Stack. However, unless you’re doing something super specific, it’s not an irreversible decision. At Wasp, we started with Stack but then migrated to Cabal since it better fit our setup and workflows. It was pretty seamless. +- One thing I do miss from other IDEs is breakpoint debugging. Technically, there’s *some* support for it in Haskell, but I don’t think many use it. Breakpoints and lazy evaluation don’t seem to be BFFs. + +## 0-60 at work + +For someone with experience in several different languages, it is pretty achievable to be able to solve minor bugs/features in Haskell after a few weeks of learning. At least, it was for me. I certainly struggled on best practices and such, and my code reviews involved some Haskell golfing comments for sure :) But I could make it do what I wanted it to do from the functionality perspective. Kudos to the mostly helpful compiler errors (with a bit of practice reading) and the Internet! + +Hopefully, your code base demonstrates established project and Haskell patterns, so you can learn as you poke around, and your early code reviewers are supportive coworkers who can explain things as part of their suggestions. I was quite fortunate in that regard: the Wasp team values teaching and learning, and the codebase uses what is called “Simple Haskell”, which limits the use of excessive language extensions in the hopes to keep the core language and concepts as tight as possible. (Note: there are Haskell experts who view this as a severe limitation of the capabilities of the language, but as a newbie, I was happy they did it.) + +## So, was the juice worth the squeeze? + +Learning Haskell took considerable time and effort. It was completely different from any language I had used before. Yet, I am very happy I embarked on this journey. Even if you do not intend to get a job using Haskell, I still think learning it is worthwhile just to expand your programming point of view and master functional concepts. And for a select set of project types (like writing a compiler for a full-stack web DSL), I feel it really will make you more productive over time. Give an intro to Haskell tutorial or video a try some weekend and let me know what you think! I’m at shayne at wasp-lang dot dev dot com. \ No newline at end of file diff --git a/web/blog/2022-09-02-how-to-get-started-with-haskell-in-2022.md b/web/blog/2022-09-02-how-to-get-started-with-haskell-in-2022.md new file mode 100644 index 000000000..ef0c6325c --- /dev/null +++ b/web/blog/2022-09-02-how-to-get-started-with-haskell-in-2022.md @@ -0,0 +1,94 @@ +--- +title: How to get started with Haskell in 2022 (the straightforward way) +authors: [martinsos] +image: /img/filip-headshot-min.jpeg +tags: [webdev, haskell, language] +--- + +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import InBlogCta from './components/InBlogCta'; +import WaspIntro from './_wasp-intro.md'; +import ImgWithCaption from './components/ImgWithCaption' + + +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](https://github.com/wasp-lang/wasp). 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](https://www.haskell.org/) 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 :)! + + + +## TLDR / Super opinionated summary + +1. For setup, use [GHCup](https://www.haskell.org/ghcup/). Install GHC, HLS, and cabal. +2. As a build tool, use [cabal](https://cabal.readthedocs.io/). +3. For editor, use VS Code with [Haskell extension](https://marketplace.visualstudio.com/items?itemName=haskell.haskell). Or, use emacs/vim/.... +4. Join [r/haskell](https://www.reddit.com/r/haskell/). Feel free to ask for help! +5. To learn the basics of Haskell, read the [LYAH](http://learnyouahaskell.com/) book and [build a blog generator in Haskell](https://lhbg-book.link/). Focus on getting through stuff instead of understanding everything fully; you will come back to it later again. + +## 1. Setup: Use GHCup for seamless installation + +[GHCup](https://www.haskell.org/ghcup/#) is a universal installer for Haskell. It will install everything you need to program in Haskell and will help you manage those installations in the future (update, switch versions, and similar). It is simple to use and works the same way on Linux, macOS, and Windows. It gives you a single central place/method to take care of your Haskell installation so that you don’t have to deal with OS-specific issues. + +To install it, follow instructions at [GHCup](https://www.haskell.org/ghcup/#). Then, use it to install the Haskell Toolchain (aka stuff that you need to program in Haskell). + +Haskell Toolchain consists of: + +1. GHC -> Haskell compiler +2. HLS -> Haskell Language Server -> your code editor will use this to provide you with a great experience while editing Haskell code +3. cabal -> Haskell build tool -> you will use this to organize your Haskell projects, build them, run them, define dependencies, etc. +4. Stack -> cabal alternative, which you won’t need for now since we’ll go with cabal as our build tool of choice + +## 2. Build tool: Use cabal + +There are two popular build tools for Haskell: [cabal](https://cabal.readthedocs.io/) and [Stack](https://docs.haskellstack.org/). Both are widely used and have their pros and cons. So, one of the hard choices beginners often face is which one to use. + +Some time ago, cabal was somewhat hard to use (complex, “dependency hell”). That’s why Stack was created: a user-friendly build tool that solves some of the common issues of cabal. (Interestingly, Stack uses cabal’s core library as its backend!) However, as Stack was being developed, cabal advanced, too. Many of its issues have been solved, making it a viable choice for beginners. + +In 2022, I recommend `cabal` to beginners. I find it a bit easier to understand when starting out (no resolvers), it works well out of the box with GHCup and the rest of the ecosystem, and it seems to be better maintained lately. + +## 3. Editor: VS Code is a safe bet + +HLS (Haskell Language Server) brings all the cool IDE features to your editor. So, as long as your editor has a decent Haskell language extension that utilizes HLS, you are good. + +The safest bet is to go with **Visual Studio Code** — it has a great [Haskell extension](https://marketplace.visualstudio.com/items?itemName=haskell.haskell) that usually works out of the box. A lot of Haskell programmers also use Emacs and Vim. I can confirm they also have good support for Haskell. + +## 4. Community: r/haskell and more + +Haskell community is a great place to ask for help and learn about new developments in the ecosystem. I prefer [r/haskell](https://www.reddit.com/r/haskell/) -> it tracks all the newest events and no question goes unanswered. There is also [Haskell Discourse](https://discourse.haskell.org/), where a lot of discussions happen, including the more official ones. A lot of Haskellers are still active on [IRC](https://wiki.haskell.org/IRC_channel), but I find it too complex and outdated to use. + +*Check [https://www.haskell.org/community](https://www.haskell.org/community) for a full list of Haskell communities.* + +## 5. Learning: You don’t need a math degree, just grab a book + +There is a common myth going around that you need a special knowledge of math (PhD in category theory!) to be able to program in Haskell properly. From my experience, this is as far from the truth as it can be. It is certainly not needed, and I seriously doubt it helps even if you have it. Maybe for some very advanced Haskell stuff, but certainly not for junior/intermediate level. + +Instead, learning Haskell is the same as learning other languages -> you need a healthy mix of theory and practice. The main difference is that there will be more unusual/new concepts than you are used to, which will require some additional effort. But these new concepts are also what makes learning Haskell so fun! + +I recommend starting with a book for beginners, [LYAH](http://learnyouahaskell.com/). It has an online version that you can read for free, or you can buy a printed version if you like physical books. + +If you don't like LYAH, consider other popular books for beginners (none of them are free though): + +1. [Haskell Programming from first principles](https://haskellbook.com/) +2. [Get Programming with Haskell](https://www.manning.com/books/get-programming-with-haskell) +3. [Programming in Haskell](https://www.amazon.com/Programming-Haskell-Graham-Hutton/dp/1316626229) + +Whatever book you go with, don’t get stuck for too long on concepts that are confusing to you, especially towards the end of the book. Some concepts will just need time to click; don’t expect to grasp it all on the first try. Whatever you do grasp from the first read will likely be more than enough to get going with your first projects in Haskell. You can always come back to those complex concepts later and understand them better. Also, don’t be shy to ask the community -> there are many Haskellers out there happy to support you in your learning! +NOTE: When I say "don't get stuck", I don't mean you should skip the difficult concept after first hurdle. No, you should spend some hours experimenting, looking at it from different angles, playing with it, trying to crack it. But you shouldn't spend days trying to understand the same concept (e.g. function as a monad) and then feel defeated due to not grasping it 100%. Instead, if you put proper effort but stuff is not completely clicking, tap yourself on the back and move on for now. + +Once you take the first pass through the book, I recommend doing a project or two. You can come up with an idea yourself, or you can follow one of the books that guide you through it. + +For example: + +1. [Learn Haskell by building a blog generator](https://lhbg-book.link/) -> free, starts from 0 knowledge, and could even be used as the very first resource +2. [The Simple Haskell Handbook](https://marcosampellegrini.com/simple-haskell-book) -> not free, expects you to know the basics of Haskell already + +Once you have more experience with projects, I would recommend re-reading your beginner book of choice. This time, you can skip the parts you already know and focus on what was confusing before. You will likely have a much easier time grasping those harder concepts. + +p.s. If you are looking for a bit of extra motivation, check the blog post my teammate Shayne recently wrote about [his journey with Haskell](https://wasp-lang.dev/blog/2022/08/26/how-and-why-i-got-started-with-haskell). He started in late 2021 and has already made huge progress! + +--- + +*Good luck with Haskell! If you have Haskell questions for me or the rest of the Wasp team, drop me a line at `“martin” ++ “@” ++ concat [”wasp”, “-”, “lang”] <> “.dev”` , or write to #haskell channel in [Wasp-lang Discord server](https://discord.gg/rzdnErX).* diff --git a/web/blog/2022-09-05-dev-excuses-app-tutrial.md b/web/blog/2022-09-05-dev-excuses-app-tutrial.md new file mode 100644 index 000000000..050ed3b6f --- /dev/null +++ b/web/blog/2022-09-05-dev-excuses-app-tutrial.md @@ -0,0 +1,272 @@ +--- +title: Building an app to find an excuse for our sloppy work +authors: [maksym36ua] +tags: [wasp] +--- + +import InBlogCta from './components/InBlogCta'; + +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](../static/img/compiling.png) + +Best excuse of all time! [Taken from here.](https://xkcd.com/303/) + + +## The requirements were unclear. + +We’ll use Michele Gerarduzzi’s [open-source project](https://github.com/michelegera/devexcuses-api). 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](https://github.com/wasp-lang/wasp/tree/main/examples/tutorials/ItWaspsOnMyMachine). + +![Final result](../static/img/final-excuse-app.png) + + +## 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](https://wasp-lang.dev/). 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](../static/img/wasp-compilation.png) + +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](https://wasp-lang.dev/docs#1-requirements) to manage multiple Node versions on your computer at the same time. + +Installing Wasp on Linux (for Mac/Windows, please [check the docs](https://wasp-lang.dev/docs#2-installation)): +``` +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](../static/img/init-page.png) + + +## 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:** + +```js title="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 + "" + ], + + 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 export +page MainPage { + component: import Main from "@ext/MainPage.js" +} + +// Prisma database entity +entity Excuse {=psl + id Int @id @default(autoincrement()) + text String +psl=} + +// Query declaration to get a new excuse +query getExcuse { + fn: import { getExcuse } from "@ext/queries.js", + entities: [Excuse] +} + +// Query declaration to get all excuses +query getAllSavedExcuses { + fn: import { getAllSavedExcuses } from "@ext/queries.js", + entities: [Excuse] +} + +// Action to save current excuse +action 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](https://wasp-lang.dev/docs/tutorials/todo-app/listing-tasks#introducing-operations-queries-and-actions). So let’s proceed with queries/actions. + + +**2) Create two files: “actions.js” and “queries.js” in the `ext` folder.** + +```js title=".../ext/actions.js | Defining an action" +export const saveExcuse = async (excuse, context) => { + return context.entities.Excuse.create({ + data: { text: excuse.text } + }) +} +``` + +```js title=".../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.** + +```js title=".../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 ( +
+
+ + + +
+
+
Saved excuses:
+ {excuses && } +
+
+ ) +} + +const ExcuseList = (props) => { + return props.excuses?.length ? props.excuses.map((excuse, idx) => ) : 'No saved excuses' +} + +const Excuse = ({ excuse }) => { + return ( +
+ {excuse.text} +
+ ) +} + +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](../static/img/final-result.png) + +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](../static/img/final-excuse-app.png) + + +## 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!](../static/img/accessible-website-excuse.jpg) + + \ No newline at end of file diff --git a/web/static/img/accessible-website-excuse.jpg b/web/static/img/accessible-website-excuse.jpg new file mode 100644 index 000000000..5d5638b77 Binary files /dev/null and b/web/static/img/accessible-website-excuse.jpg differ diff --git a/web/static/img/compiling.png b/web/static/img/compiling.png new file mode 100644 index 000000000..1577dcf31 Binary files /dev/null and b/web/static/img/compiling.png differ diff --git a/web/static/img/filip-headshot-min.jpeg b/web/static/img/filip-headshot-min.jpeg new file mode 100644 index 000000000..350329bad Binary files /dev/null and b/web/static/img/filip-headshot-min.jpeg differ diff --git a/web/static/img/final-excuse-app.png b/web/static/img/final-excuse-app.png new file mode 100644 index 000000000..d7e912151 Binary files /dev/null and b/web/static/img/final-excuse-app.png differ diff --git a/web/static/img/final-result.png b/web/static/img/final-result.png new file mode 100644 index 000000000..43e55c7a1 Binary files /dev/null and b/web/static/img/final-result.png differ diff --git a/web/static/img/init-page.png b/web/static/img/init-page.png new file mode 100644 index 000000000..cdf283ce3 Binary files /dev/null and b/web/static/img/init-page.png differ