We Created a Tool to Plan Our Breakfasts
Breakfast with your friends and colleagues always involves a certain amount of organization. After a date has finally been found that works for everyone, there are still some things that need to be arranged:
Figure out who attends
Ask everyone what and how much they want to eat and drink
Communicate pricing to your guests
Collect money and check if everyone has paid
Write a shopping list
Typically, this is then organized in a WhatsApp group or in a Slack channel and quickly becomes confusing. These problems are solved by our app Weißwurst Planer, which supports its users in organizing meals together. The following describes how we have tried to implement the requirements for the project in such a way that it is as easy and data-saving to use as possible.
It was important to us that the app is as simple and straightforward to use as possible, which is why we decided to use only the most necessary data from users for breakfast planning. As a template, we used the scheduling app Doodle, except that instead of scheduling it is about organizing breakfasts. For this purpose, we have considered the following requirements, which are also implemented by the app in this way:
There is an event host that can add an event. After creating the event, he receives an individual link that he can send to all guests, who can then add their orders.
Besides the organizational data (date, time, event name, description), the host can individualize the product range. For this purpose, he can either use the products suggested by us or add his own products with a price to the event. This allows users to plan other shared meals, such as dinner.
Guests can add their order under the event link. To make it as easy as possible for the guests, they only need their name (and no account). Nevertheless, they can still view, edit or delete their order at a later time, as the necessary data is stored in the guests' localStorage.
Unlike the guests, the event host has an account in the Weißwurst Planer app. This ensures that he can access and manage the data of his created events in any case.
The host can edit or delete all orders if a user loses access to his order.
After the order acceptance is closed by the host, he can view the shopping list. In addition, there is a checklist with all participants on which can be tracked who have already paid.
The app selects one person from all participants to purchase the order.
The requirements for the backend consist of the connection to the database, user authentication, and the implementation of the business logic. To keep the effort and development time of the project as low as possible, we decided to use backend as a service. We implemented this with AWS Amplify, which generates most of the code needed to communicate with the backend (e.g. GraphQL queries) automatically. Besides Amplify, there are also alternatives, such as Firebase. Amplify is considered a bit more flexible compared to Firebase and also offers a GraphQL API in addition to a REST API. The following describes how we used the various AWS services to implement our project with some code examples.
Amplify uses Amazon Cognito for user authentication. In addition to the login via the email address or the Amazon account, logins via Apple and Google are also accepted. For our project, we only use the login methods email and Google Account. For this, we first added auth to our app via the Amplify CLI using the amplify add auth
command. For authentication handling in the frontend, Amplify offers two options: You can use the pre-built UI components or call the authentication APIs manually. In our project, we use both options.
For log-in and sign-up handling, we use pre-built UI components from Amplify. For this, Amplify provides a withAuthenticator
higher-order component (HoC) in React projects, which wraps the corresponding components. This ensures that when the component wrapped with the HoC is mounted, the log-in/sign-up screen is shown if the user is not logged in. In addition, the withAuthenticator
HoC passes the props user
and signOut
to the wrapped component.
To access the user's data in other components we use the API provided by the Auth
class. This provides over 30 methods, such as changePassword
or signOut
. For easier access to the methods relevant to us, we have created a custom React hook:
In addition, the current login state is stored in the React context to avoid waiting time to access the API.
To connect an API and a database to the app, the Amplify CLI provides the amplify add api
command. After configuring the API in the CLI, the GraphQL schema is autogenerated in the app. This schema can then be edited and deployed using the amplify push
command. Alternatively, one can use the graphical editor in the Amplify Studio web interface for this purpose. In addition to the GraphQL schema, various mutations, subscriptions, and queries are also generated.
To access the API in the frontend, the API
class of aws-amplify
and the GraphQL operations (e.g. getEventsOfAdmin
from the autogenerated ../../graphql/queries
folder) must be imported. Then the graphql
method of the API class can be called with the corresponding query and input parameters. The following example shows the fetching of all events of an event host with its unique ID (= adminId
):
Of course, the autogenerated GraphQL queries, subscriptions, and mutations cannot cover all use cases. Therefore we added lambda functions to our project. This works with the Amplify CLI command amplify add function
. After that, you can customize the properties of the lambda function, like the name or the access rights to the API. To access the lambda function in the frontend, it must be added to the GraphQL schema under the appropriate type (Query
, Mutation
or Subscription
) using the @function
directive. This looks like this in the example of the getEventsOfAdmin
query used above:
Inside the lambda, we can then also (if configured when adding the lambda) use the AppSync GraphQL API. Within the lambda, we can then access the GraphQL endpoint with process.env.API_<YOUR_API_NAME>_GRAPHQLAPIENDPOINTOUTPUT
and the API key with process.env.API_<YOUR_API_NAME>_GRAPHQLAPIKEYOUTPUT
. This looks like this in the example of our getEventsOfAdmin
lambda:
Amplify also provides the ability to mock and test changes locally. This is possible for the API, the storage, and the lambda functions. This makes it very easy to test and debug changes locally. The lambda described above could then be tested with the command amplify mock function getEventsOfAdmin
for example. The input parameters must be added to a JSON file, which is generated by default when creating a new lambda inside the src
folder.
If you look at the package.json you will notice that besides the dependencies directly related to React or Amplify there are only the following entries:
styled-components: Helps to style the project at the component level with a mixture of JavaScript and CSS
date-fns: Provides helpful functions to simplify the handling of dates in JavaScript
react-router-dom: Provides client-side handling of the routing
react-hook-form: Helps to deal with forms in JavaScript. This helped us with the implementation of the CreateEventForm.
react-helmet: Enables dynamic modification of the document head
Everything else we implemented ourselves. This also applies to the state and context management for which we used the React Context API.
To sum up, we could realize all the predefined requirements with Amplify's help. In doing so, most of the services used by Amplify could be configured either directly in the CLI or in Amplify Studio. Everything else we were able to set up fairly easily on the individual services' pages in the AWS console. In addition, there is now also a relatively large community that can usually answer any questions about Amplify related to smaller projects. We would be happy if you remember our app the next time you plan a breakfast with your friends and colleagues and we can help you organize it.