Build an event keeper app with Ionic and AWS
Tags: aws, ionic, tutorial
Published: 2020-07-30 14:42:14

---
title: Build an event keeper app with Ionic and AWS
published: true
date: 2020-07-23 17:30:11 UTC
tags: aws,ionic,tutorial
canonical_url: https://blog.logrocket.com/build-an-event-keeper-app-with-ionic-and-aws/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/bgy77zskhsfo63h19ypq.png
---
**Written by [Wisdom Ekpot](https://blog.logrocket.com/author/wisdomekpot/)**✏️

The Ionic framework is an open-source UI toolkit for building performant, high-quality mobile and desktop apps using web technologies (HTML, CSS, and JavaScript) with integrations for popular frameworks such as Angular, Vue, and React.

Amazon Web Services (AWS) provides on-demand cloud platforms on a pay-as-you-go basis.

In this tutorial, we’ll build an event keeper app using an AWS service called S3 to save our images. All images uploaded to our platform will be stored in the AWS S3 bucket and the URL of our image will be returned.

## Prerequisites

To follow along with this tutorial, you’ll need:

- A basic understanding of HTML, CSS, and JavaScript (ES6+)
- A code editor, such as VS Code, installed
- POSTMAN installed
- MongoDB set up
- Basic knowledge of [Angular](https://dev.to/logrocket/angular-10-is-now-available-here-s-what-you-need-to-know-2756) and Express.js

## Building the backend

To build the backend, complete the following steps.

1. Initialize a new Node project
2. Install all the required dependencies
3. Create an Express server and configure the dependencies
4. Create and configure MongoDB
5. Create the event keeper model
6. Set up AWS and Multer
7. Create the necessary routes and controllers
8. Test the routes using POSTMAN

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Initialize a new Node project

Let’s start by creating a folder on the desktop for our application. Open up your terminal and type the following.

```jsx
cd desktop
mkdir event-app && cd event-app
mkdir server && cd server
code
```

Here we’re creating an `event-app` directory on the desktop. Inside the `event-app` directory, we’ll create a `server` directory. This is where we’ll set up our backend application.

The `code .` command will set up our application in VS Code. VS Code is redefined and optimized for building and debugging modern web and cloud applications. Feel free to use any text editor of your choice. We’ll use the VS Code integrated terminal for the purpose of this tutorial.

To set up a Node.js application, we’ll need a `package.json` file to manage all our project dependencies. To create that, run `npm init -y` on the terminal.

```jsx
npm init -y
```

This will create a `package.json` file.

## Install required dependencies

Our application will need the following npm packages for development.

- `Expressjs` — a node.js framework that makes it easy to build web applications.
- `mongoose` — an object modeling tool used to asynchronously query MongoDB that provides a straightforward, schema-based solution to model your application data
- `body-parser` — a pachage that allows Express to read the request body and then parse that into a JSON object that can be understood
- `cors` — a middleware that can be used to enable CORS with various options
- `morgan` — a logging tool that logs all routes and requests
- `aws-sdk` — the official AWS SDK for JavaScript, available for browser, mobile devices, and Node.js backends
- `multer` — a Node.js middleware for handling multipart form data, which is primarily used for uploading files
- `dotenv` — a zero-dependency module that loads environment variables from an `.env` file into `process.env`. This is where we’ll store all outher environment variables for our application, such as AWS secret, ID, etc.
- `multer-s3` — a package used for streaming the Multer storage engine for AWS S3
- `nodemon` a package that reruns the Express server every time we make changes to our code

Start by installing Nodemon globally so that it can be accessed anywhere on your local machine.

```jsx
npm install -g nodemon
```

Next, install all the other dependencies.

```jsx
npm i --save express body-parser cors morgan aws-sdk multer multer-s3 mongoose dotenv
```

This command will install all these packages and add them to our dependencies in the `package.js file`. It also creates a `node-modules` directory, this is where all our packages are being stored.

Remember not to add this package while you’re pushing your project to git. To avoid this, create a `.gitignore` file

```jsx
touch .gitignore
```

This command will create a `.gitignore` file. Open the file and type `node-modules` in it.

## Create an express server and configure all dependencies.

To set up the Express server, create a `src` directory and place an `index.js` file inside it. This is the base file for our application. We’ll define our first route here and configure some packages.

Add the following code to the `index.js` file.

```jsx
require('dotenv').config()
const express = require("express");
const PORT = process.env.PORT || 4000;
const morgan = require("morgan");
const cors = require("cors");
const bodyParser = require("body-parser");
const app = express();
//--------------registering cors -----------------
app.use(cors());
//----------------configure body parser --------------
app.use(bodyParser.urlencoded({
    extended: false
}));
app.use(bodyParser.json());
//---------------configure morgan  --------
app.use(morgan("dev"));

// define first route
app.get("/", (req, res) => {
    res.json({
        data: "Hello MEIN developers...The `I` stands for Ionic😎😎"
    });
});
app.listen(PORT, () => {
    console.log(`App is running on ${PORT}`);
});
```

Open the `package.json` file and add the following. Running `npm run dev:start` will bootstrap our application.

```jsx
"scripts": {
    "start:dev":"nodemon src"
  },
```

If you open up the application on the browser, you should see this:

![Open App in Browser](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/open-app-in-browser.png?resize=720%2C216&ssl=1)

## Create and configure the MongoDB database

Create a `config` directory. Inside the `config` directory, create a `mongoose.js` file and add the following code to the file.

```jsx
const mongoose = require("mongoose");
module.exports = function (app) {
    mongoose.connect("mongodb://localhost:27017/ionic-app", {
        useUnifiedTopology: true,
        useNewUrlParser: true,
        useFindAndModify: false
    }).then(res => console.log("Database conneceted")).catch(err => console.log(err))
    mongoose.Promise = global.Promise;
    process.on("SIGINT", cleanup);
    process.on("SIGTERM", cleanup);
    process.on("SIGHUP", cleanup);
    if (app) {
        app.set("mongoose", mongoose);
    }
};
function cleanup() {
    mongoose.connection.close(function () {
        process.exit(0);
    });
}
```

We need to register it in our root file and then pass the instance of `express()`. To do this, we need to require this module in our `index.js` file.

```jsx
require("./config/mongoose")(app);
```

Once you do this, you’ll see `Database conneceted` logged on the console if your application is running.

## Create the event keeper model

Now that our database is ready, we need to create our Mongoose schema, which takes in an object that defines the property of the event schema.

Create a directory to hold the schema, routes, and controllers.

```jsx
cd src
mkdir api && cd api
mkdir events && cd events
touch model.js && touch controller.js && touch routes.js
```

Let's define our events schema by add this codes in our `events/model.js` file:

```jsx
const mongoose = require("mongoose");
const eventSchema = mongoose.Schema({
    name: {
        type: String,
        required: true
    },
    image: {
        type: String,
        required: true
    },
    description: {
        type: String,
        required: true
    }
}, {
    timestamps: true,
});
const Event = mongoose.model("Event", eventSchema);
module.exports = Event;
```

Mongoose converts the schema into a document in the database. Those properties are then converted into fields in the document.

## Set up AWS and Multer

First, you’ll need to register for an [AWS account](https://aws.amazon.com).

Once you log in, you’ll see `services` link on the navigation bar. Click on it and type “s3” into the search box.

![AWS Services Search Page](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/aws-services-search-page.png?resize=720%2C390&ssl=1)

Click on S3. This is what we’ll use for our cloud storage.

Create a new bucket for your application by clicking “create bucket.” A modal will pop up asking you to specify the name of your bucket. Call your bucket `eventskeeper` and choose `US West (N. California)` as your region.

Go to the “Permissions” section and uncheck the checkbox to block all public access, then save your changes.

![AWS Services Permissions Tab](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/aws-services-permissions-tab.png?resize=720%2C341&ssl=1)

Nex, we need to get all our security credentials. Click on the logged-in user’s name at the top of the page and then click “My Security Credentials.”

![AWS Management Console](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/aws-management-console.png?resize=720%2C312&ssl=1)

This will take you to a page where you can set your security credentials. Click “Access Keys” and then “Create New Access Keys.” You’ll see a modal that contains your security details. Copy these details so you can store them in an `.env` file.

Create the `.env` file.

```jsx
touch .env
```

Inside the `.env` file, add the following.

```jsx
ACCESS_KEY=****************ZZN7SQDUJUPQ
ACEESS_SECRET=**********************ifn30**
```

Replace the values with the security details you got from the site. Don’t forget to add this file to the `.gitignore` file.

Create a `aws.js` file inside your config directory and add the following.

```jsx
const aws = require('aws-sdk')
const multer = require('multer')
const multerS3 = require('multer-s3')


aws.config.update({
    secretAccessKey: process.env.ACEESS_SECRET,
    accessKeyId: process.env.ACCESS_KEY,
    region: 'eu-west-3'
})
const s3 = new aws.S3();
const fileFilter = (req, file, cb) => {
    if (file.mimetype.startsWith('image')) {
        cb(null, true);
    } else {
        cb(new Error('Not an image! Please upload an image.', 400), false);
    }
};
const upload = multer({
    fileFilter,
    storage: multerS3({
        s3,
        bucket: 'eventskeeper',
        acl: 'public-read',
        metadata: function (req, file, cb) {
            cb(null, {
                fieldName: "TESTING_META_DATA"
            });
        },
        key: function (req, file, cb) {
            cb(null, Date.now().toString())
        }
    })
})
module.exports = {
    upload,
    s3
}
```

Start by importing `aws-sdk`,`multer`, and `multer-s3` into this file. Next, use the credentials stored in the environment variable to configure your `aws-sdk` package. Replace your region with the region in the config object.

The next step is to create a simple filter for your file upload that will only accept file types in which the `mimetype` starts from `image`.

After defining this filter, create a Multer instance to take in the filter and the bucket config to which we’ll be sending our files. In the `multerS3` instance, specify the name of the bucket that will hold your file, then set `alc` to `public read` so that you can add files at any time.

To use these methods, we need to export the functions so that it can be accessible by any JS module.

## Create the necessary routes and controllers.

We basically have two routes: one to get all the events and the other to add a new event.

In the `api/events/routes.js` file, add the following routes.

```jsx
const eventController = require("./controller");
const router = require("express").Router();

router.post("/", eventController.addEvent)
router.get("/", eventController.getAllEvents)


module.exports = router;
```

We have to create the `addEvent` and `getAllEvents` methods in our controller file. Start by creating the `addEvent` method in the `api/events/controller.js` file.

```jsx
let Event = require('./model');
const {
    upload,
    s3
} = require('../../config/aws')

const singleUpload = upload.single('image');
exports.addEvent = async (req, res) => {
    try {
        await singleUpload(req, res, err => {
            if (err) {
                return res.status(422).json({
                    title: "File Upload Error",
                    desription: err.message
                })
            }
            const event = new Event({
                name: req.body.name,
                description: req.body.description,
                image: req.file.location
            })
            event.save();
            res.status(200).json({
                status: "Success",
                mes: "New Event Created"
            })
        })
    } catch (err) {
        res.status(500).json({
            status: "error",
            error: err
        })
    }
}
```

We also need to bring in our event module and the AWS config module. Start by checking whether there are any errors while trying to upload the image, and throw a `422` error for any unprocessable entity. If there are no errors, use the `req.body` to access the JSON data that was sent in the request and use the `req.file.location` to get the URL of the image.

Next, create the `getAllEvents` function. This will basically get all the events stored in MongoDB.

```jsx
exports.getAllEvents = async (req, res) => {
    try {
        let events = await Event.find();
        res.status(200).json({
            status: "Success",
            data: events
        })
    } catch (err) {
        res.status(500).json({
            status: "error",
            error: err
        })
    }
}
```

Use the `find()` method to fetch all data stored in the DB.

Now we need to register a global route handler for our events. Create a `routeHandler.js` file inside the `api` directory and add the following code.

```jsx
const EventRoutes = require("./events/routes");
module.exports = (app) => {
    app.use("/event", EventRoutes);
}
```

Next, register it in the `index.js` file.

```jsx
require("./api/routehandler")(app);
```

It’s time to test our application on POSTMAN.

### Posting an event

![Post an Event](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/post-event.png?resize=720%2C467&ssl=1)

### Getting all events

![Get All Events](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/get-all-events.png?resize=720%2C473&ssl=1)

## Setting Up the Mobile App

Install Ionic and other Ionic tools globally.

```jsx
npm install -g @ionic/cli native-run cordova-res
```

Create a new directory called `events` on your desktop. Init a new Ionic application and Angular inside that directory.

```jsx
cd desktop
cd event-app && mkdir events && cd events
ionic start events tabs --type=angular --capacitor
```

This Ionic Angular app uses the Tabs starter template and adds Capacitor for native functionality.

Now we can move into our application by running:

```jsx
cd events
```

The Ionic PWA elements library will provide the web-based functionality in our application. To install it, run the following command inside your application directory.

```jsx
npm install @ionic/pwa-elements
```

To use this package, we need to import it into our `src/main.ts` file.

```jsx
import { defineCustomElements } from '@ionic/pwa-elements/loader';

// Call the element loader after the platform has been bootstrapped
defineCustomElements(window);
```

If you don't have Ionic lab already installed on your application, you can run `npm i -D -E @ionic/lab` on your terminal to install it.

```jsx
ionic serve --lab
```

This will open up our application port 8100 in our browser.

![App Open on Port 8100](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/app-open-on-port-8100.png?resize=720%2C387&ssl=1)

## Setting up the user interface

It’s time to set up the user interface. We’ll start by customizing the tabs.

Head over to `src/app/tabs/tabs.page` and modify the code there like so:

```jsx
<ion-tabs>
  <ion-tab-bar slot="bottom">
    <ion-tab-button tab="tab1">
      <ion-icon name="home-outline"></ion-icon>
      <ion-label>Events</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="tab2">
      <ion-icon name="add-outline"></ion-icon>
      <ion-label>Add Event</ion-label>
    </ion-tab-button>
  </ion-tab-bar>
</ion-tabs>
```

We’ll use cards to represent our events, so now head over to `src/app/tab1/tab1.page.html` and modify the code there with the following.

```jsx
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Events
    </ion-title>
  </ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
  <ion-card>
    <img style="width:100%;height:250px;object-fit: cover;"
      src="https://images.pexels.com/photos/3323208/pexels-photo-3323208.jpeg?cs=srgb&dl=woman-leaning-on-sinalco-cola-can-3323208.jpg&fm=jpg"
      alt="">
    <ion-card-header>
      <ion-card-subtitle>Card Subtitle</ion-card-subtitle>
      <ion-card-title>Card Title</ion-card-title>
    </ion-card-header>
    <ion-card-content>
      Keep close to Nature's heart... and break clear away, once in awhile,
      and climb a mountain or spend a week in the woods. Wash your spirit clean.
    </ion-card-content>
  </ion-card>
</ion-content>
```

The result of the modification should look like this:

![Modified Code Result](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/modified-code-result.png?resize=720%2C431&ssl=1)

Create a form in the `tabs2` component for adding a new event. Add the following to `src/app/tab2/tab2.page.html`.

```jsx
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Add Event
    </ion-title>
  </ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true" class="ion-padding">
  <div id="container">
    <form>
      <ion-item id="rounded">
        <ion-label position="stacked">Name</ion-label>
        <ion-input id="input"></ion-input>
      </ion-item>
      <ion-item id="rounded">
        <ion-input id="photo" name="file" type="file" accept="image/*" (change)="handleFileInput($event.target.files)">
        </ion-input>
      </ion-item>
      <ion-item>
        <ion-label position="stacked">Description</ion-label>
        <ion-textarea rows="6" cols="20"></ion-textarea>
      </ion-item>
      <div class="ion-padding">
        <ion-button expand="block" type="submit" class="ion-no-margin">Add An Event</ion-button>
      </div>
    </form>
  </div>


</ion-content>
```

Next, add some styles to `src/app/tab2/tab2.page.scss`.

```jsx
#rounded {
    width: 100%;
    margin-top: 30px;
    -moz-border-radius: 50px;
    -webkit-border-radius: 50px;
    border-radius: 10px;
    margin-bottom: 10px;
}
```

![Add Styles to Events](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/add-styles.png?resize=720%2C438&ssl=1)

## Creating the event service

The next step is to create a service to handle all the HTTP requests for our application.

```jsx
ionic generate service api/event
```

Running this command will generate an `api` directory. Inside the `api` directory, modify the `event.service.ts` like so:

```jsx
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class EventService {
  readonly rootURL: string = 'http://localhost:4000';
  getAllEvents() {
    return this.http.get(`${this.rootURL}/event`);
  }
    addNewEvent(payload) {
      return this.http.post(`${this.rootURL}/event`, payload);
    }
  constructor(private http: HttpClient) {}
}
```

Next, register Angular’s `HttpClientModule` in your `src/app/app.module.ts`.

```jsx
import { HttpClientModule } from '@angular/common/http';
```

Then, add it to the `imports` array.

```jsx
  imports: [...other packages, HttpClientModule]
```

## Implementing The APIs

We can now bring our services into our `tab1` and `tab2` components. Edit the code in `src/app/tab1/tab1.page.ts` with the following.

```jsx
import { Component } from '@angular/core';
import { EventService } from '../../app/api/event.service';
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: [ 'tab1.page.scss' ]
})
export class Tab1Page {
  constructor(private Event: EventService) {}
  events = [];
  getEvents() {
    console.log('hello');
    this.Event.getAllEvents().subscribe((data: any) => {
      this.events = data.data.reverse();
//the reverse method will get from the latest event added
    });
  }
  ngOnInit(): void {
    //Called after the constructor, initializing input properties, and the first call to ngOnChanges.
    //Add 'implements OnInit' to the class.
    this.getEvents();
  }
}
```

Basically, what we’re doing here is bringing in our events service, then creating a state to hold all the events. We’re also creating a new method to query for all events and registering it in the `ngOnInit` lifecycle hook.

We’ll display our events on the card on our template by modifying the code.

```jsx
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Events
    </ion-title>
  </ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
  <ion-card *ngFor="let event of events">
    <img style="width:100%;height:250px;object-fit: cover;" [src]="event.image" alt="">
    <ion-card-header>
      <ion-card-title>{{event.name}}</ion-card-title>
      <ion-card-subtitle>{{event.createdAt |date}}</ion-card-subtitle>
    </ion-card-header>
    <ion-card-content>
      {{event.description}}
    </ion-card-content>
  </ion-card>
</ion-content>
```

Doing this will display all the events in our database.

![All Events in the Database](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/07/all-events-in-database.png?resize=720%2C439&ssl=1)

## Implementing Add Events

In javascript we use `formData` to handle file uploads.We will use the angular reactive forms to handle the file uploads.We will start by importing `FormsModule` and `ReactiveFormsModule` modules in our `src/app/tab2/tab2.module.ts` file:

```jsx
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
imports: [... other modules, FormsModule,ReactiveFormsModule, ],
```

We can now use Angular’s reactive forms in our application components. Import `FormGroup` , `FormControl`, and `EventService` into the `tab2.page.ts` file and define an instance of the form using the `formControl` module.

```jsx
import { FormGroup, FormControl } from '@angular/forms';
import { EventService } from '../../app/api/event.service';


//this should be added inside the Tab2Page Class
eventForm = new FormGroup({
    name: new FormControl(''),
    description: new FormControl('')
  });
  constructor(private Event: EventService) {}
```

We need to define a method to handle our input field for file upload. This will be a change event that will watch when a file is selected. Your input for file upload should look like this:

```jsx
 <ion-input id="photo" name="file" type="file" accept="image/*" (change)="handleFileInput($event.target.files)">
        </ion-input>
```

Next, create a state to hold the file and define the `handleFileInput` method.

```jsx
  fileToUpload: File = null;
  handleFileInput(files: FileList) {
    this.fileToUpload = files.item(0);
    console.log(this.fileToUpload);
  }
```

Now if you select an image to upload it, will be logged to the console. Try it out!

Next, create a method to add a new event. Add the following method to the `tab2.page.ts` file.

```jsx
    addEvent() {
        const formData: FormData = new FormData();
        formData.append('image', this.fileToUpload, this.fileToUpload.name);
        formData.append('name', this.eventForm.value.name);
        formData.append('description', this.eventForm.value.description);
        this.Event.addNewEvent(formData).subscribe(
            (data: any) => {
                console.log(data);
            },
            (err: HttpErrorResponse) => {
                console.log({ error: err });
            }
        );
    }
```

Use the form data to append the data to your input fields and then pass the `formData` in the `addNewEvent` service as a payload.

After doing this, modify your `tab2` form to the following.

```jsx
  <form [formGroup]="eventForm" (submit)="addEvent()">
      <ion-item id="rounded">
        <ion-label position="stacked">Name</ion-label>
        <ion-input formControlName="name" id="input" required></ion-input>
      </ion-item>
      <ion-item id="rounded">
        <ion-input id="photo" type="file" accept="image/*" (change)="handleFileInput($event.target.files)">
        </ion-input>
      </ion-item>
      <ion-item>
        <ion-label position="stacked">Description</ion-label>
        <ion-textarea formControlName="description" rows="6" cols="20" required></ion-textarea>
      </ion-item>
      <div class="ion-padding">
        <ion-button expand="block" type="submit" class="ion-no-margin">Add An Event</ion-button>
      </div>
    </form>
```

Now refresh your application and try adding a new event.

## Adding spinners and toast

We need to add preloaders and a toast so we can show users whether the add event methods was successful or not. The toast will log both success and error messages.

Import `LoadingController` and `ToastController` and then register them in the constructors.

```jsx
import { LoadingController } from '@ionic/angular';
import { ToastController } from '@ionic/angular';

constructor(
        private Event: EventService,
        public loadingController: LoadingController,
        public toastController: ToastController
    ) {}
```

We have to define a method to show and hide our loaders and also show our toasts.

```jsx
    async presentLoading() {
        const loading = await this.loadingController.create({
            message: 'Please wait...',
            translucent: true
        });
        return await loading.present();
    }
    async presentToast(message) {
        const toast = await this.toastController.create({
            message: message,
            duration: 2000
        });
        return toast.present();
    }
```

We can now modify our `addEvent` method.

```jsx
addEvent() {
        this.presentLoading();
        const formData: FormData = new FormData();
        formData.append('image', this.fileToUpload, this.fileToUpload.name);
        formData.append('name', this.eventForm.value.name);
        formData.append('description', this.eventForm.value.description);
        this.Event.addNewEvent(formData).subscribe(
            (data: any) => {
                this.loadingController.dismiss();
                this.eventForm.reset();
                this.presentToast('Event Added');
            },
            (err: HttpErrorResponse) => {
                this.loadingController.dismiss();
                this.presentToast('Something Went Wrong');
                console.log({ error: err });
            }
        );
    }
```

Congratulations! You just built a simple event app.

## Conclusion

In this tutorial, we demonstrated how to build a simple application with Ionic. We could add better features to our app, such as user sign-in and sign-up. Understanding how both backend and mobile apps communicate will help you build larger, more complex and feature-rich applications.

For source code used in this tutorial, head to [GitHub](https://github.com/Wisdom132/Ionic-and-AWS-app).
* * *
The post [Build an event keeper app with Ionic and AWS](https://blog.logrocket.com/build-an-event-keeper-app-with-ionic-and-aws/) appeared first on [LogRocket Blog](https://blog.logrocket.com).