Glimpz: The Ultimate Music Discovery App | AWS Amplify Hackathon

#awsamplify #awsamplifyhackathon
Introduction
Before we dive into the techy details, let's set the stage with a quick intro to Glimpz Music. Glimpz is where music discovery becomes a breeze, with short and sweet song previews and a handy sync-up to your Spotify playlist. Just a glimpse of the harmony I've achieved using a robust tech stack. Curious about how I made this happen? Let's get into it!
Feeling impatient, like me? Skip the chatter and try Glimpz Music straight away!
For my fellow developers, here's the client build for Glimpz
Why AWS Amplify and more...
Tech stack and frameworks for Glimpz
For the server-side infrastructure, I used -
AWS DynamoDB - database management
AWS Appsync (GraphQL) - APIs
AWS Lambdas - Utility Python scripts and custom API logic
AWS Kinesis & Pinpoint - Data analytics on user behavior and trends
AWS Opensearch - quick text-based searching of music-related metadata (elastic search under the hood).
For the client-side build, I used React Typescript with the Ionic framework. The versatality of Ionic allowed me to build once, and deploy on all major platforms (iOS, Android, and the Web).
Building a full-stack app involves a lot of moving parts, from handling authentication to managing storage and APIs. AWS Amplify makes this process straightforward by gluing all of these microservices and frameworks together in a super easy way.
In this article, we'll explore how to use AWS Amplify to incorporate various services like Authentication, APIs, Storage, Kinesis, and Pinpoint in your app.
Getting Started
First, ensure you have installed and configured the AWS Amplify CLI. You can install it using npm (Node.js package manager) as follows:
npm install -g @aws-amplify/cli
amplify configure
This will guide you through the process of setting up the Amplify CLI with your AWS account.
Creating a new AWS Amplify project
Once you have the Amplify CLI configured, you can initialize a new Amplify project by running:
amplify init
You will be prompted to provide some information about your project (such as the project's name) and AWS settings.
Amplify Authentication (Auth)
Amplify Auth module provides an interface for authenticating a user. To add authentication capabilities to your app, you can use the following command:
amplify add auth
This will enable AWS Cognito, which provides authentication services for your app.
Amplify API (AppSync GraphQL)
To provide real-time data and seamless user experiences with efficient data queries and subscriptions. These APIs played a crucial role in enhancing Glimpz's capabilities, enabling a seamless and engaging music discovery experience for users.
Adding a GraphQL API with AWS AppSync to your app can be done using the following command:
amplify add api
You will be guided through the process to create your API, defining your schema, and setting up any authorization rules.
Data Modeling
Here's how I've modeled my data schema for Glimpz's various use cases with APIs.
input AMPLIFY { globalAuthRule: AuthRule = { allow: public }}
type Album @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
id: Int! @primaryKey
title: String!
reldate: String
tracks: [Track] @hasMany
}
type Track @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
id: Int! @primaryKey
name: String! @index(name: "byTitle", queryField: "trackByTitle")
artistCredit: Int
gid: ID
min_date: Int
isrc: String
artists: String
genre: String
label: String
ttype:String
album: Album
rel_artists: [Artist] @manyToMany(relationName: "TrackArtist")
}
type Artist @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
id: Int! @primaryKey
gid: ID
name: String! @index(name: "byName", queryField: "artistByName")
yob: Int
type: Int
tracks: [Track] @manyToMany(relationName: "TrackArtist")
}
type Card @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
id: Int! @primaryKey
albumgenres: String
albumhref: String
albumid: String
albumlabel: String
albumname: String
albumpopularity: Int
albumtype: String
coverart_a: String
coverart_b: String
coverart_c: String
explicit: Boolean
external_urls: String
genres: String
jsonartists: String!
min_date: String
popularity: Int
sartists: String!
totaltracks: Int
title: String!
spotifyid: String! @index(name: "bySpotifyid", queryField: "bySpotifyid")
trackurl: String!
snippeturl: String
uri: String!
decks: [Deck] @manyToMany(relationName: "DeckCard")
}
type Deck @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
id: Int! @primaryKey
version_id: Int!
title: String! @index(name: "byTitle", queryField: "byTitle")
dtitle: String
dkType: DeckType
genre: String
src: String
creatorID: Int! @index(name: "getDeckByCreatorID", queryField: "getDeckByCreatorID")
updatedAt: AWSDate
cards: [Card] @manyToMany(relationName: "DeckCard")
# @index(name: "getDeckByCreatorID", queryField: "getDeckByCreatorID")
}
type AdjacencyGroup @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
id: Int! @primaryKey
title: String! @index(name: "byGrpTitle", queryField: "byGrpTitle")
source: String
decks: [Deck]
}
type UserTracks @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
userID: Int! @primaryKey(sortKeyFields: ["trackID"])
trackID: Int!
# liked will be 0 (false) or 1 (true)
liked: Int!
# source is typically deckID
source: Int
}
type deckHistory @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
userID: Int! @primaryKey(sortKeyFields: ["deckID"])
deckID: Int!
# liked will be 0 (false) or 1 (true)
liked: Int!
songsHeard: Int
}
type UserSummary @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
userID: Int! @primaryKey
# basic info
numLike: Int
numSkip: Int
numDeck: Int
numShare: Int
numTotEv: Int
# deck info
FavDeckID: Int
LatestDeckID: Int
# session info
LatestSessionDate: Int
LatestSessionDur: Int
AvgSessionDur: Int
TotalSessions: Int
}
type Recos {
name: String!
decks: [Deck]!
}
type Category {
decks: [Deck]
}
type Hero @model(timestamps: null) @auth(rules: [
{ allow: public, provider: apiKey }
])
{
uid: Int! @primaryKey
deckID: Int!
}
type SearchRes {
decksList: [Deck]
otherDecks: [Deck]
}
type ApiContext{
userID: String!
api: String!
}
type ClientDeck {
deck: Deck
expand: Boolean
}
enum DeckType{
STATIC
DYNAMIC
PERSONALIZED
}
type Query {
getRecos2(userID: Int, userPreferences: [String]): [Recos] @function(name: "getRecos2-${env}")
getDeckSource(deckID: Int): String @function(name: "getDeckSource-${env}")
getLikedSongs(userID: Int): [Card] @function(name: "getLikedSongs-${env}")
getTrendingDecks(userID: Int): [Deck] @function(name: "getTrendingDecks-${env}")
getFavoriteDecks(userID: Int): [Deck] @function(name: "getFavoriteDecks-${env}")
getHeroDeck(userID: Int): Deck @function(name: "getHeroDeck-${env}")
}
type Mutation {
swipeLeft(userID: Int, trackID: Int, deckID: Int): String @function(name: "putSwipeLeft-${env}")
swipeRight(userID: Int, trackID: Int, deckID: Int): String @function(name: "putSwipeRight-${env}")
addLikedSong(userID: Int, trackID: Int): String @function(name: "addLikedSong-${env}")
addFavoriteDeck(userID: Int, deckID: Int): String @function(name: "addFavoriteDeck-${env}")
}
input AMPLIFY { globalAuthRule: AuthRule = { allow: public }}). However, in a real-world production application, this is typically not recommended due to security reasons. Instead, you'd usually restrict access to authenticated users or specific roles and/or groups.Amplify Storage (S3)
To store and deliver 30-second snippets of every song to users, I leveraged the power of Amazon S3. This ensures a seamless and immersive music discovery experience, allowing users to quickly preview and explore a vast library of tracks with ease.
With S3's reliable and scalable storage capabilities, Glimpz delivers snippets of your favorite tunes effortlessly.
Amplify provides an easy way to incorporate cloud storage using AWS S3. To add storage capabilities to your app, run:
amplify add storage
Follow the prompts to configure your storage, such as naming your S3 bucket and setting up access permissions.
Amplify Analytics (Pinpoint and Kinesis)
To gain valuable data analytics on users and their preferences, I harnessed the potential of Amazon Pinpoint and Amazon Kinesis. Pinpoint enabled me to deliver targeted notifications and personalized recommendations, while Kinesis efficiently processed and analyzed the streaming data, helping me identify trends and fine-tune the product for a perfect fit in the market.
Amplify makes it simple to gather data on user behavior with Amazon Pinpoint and real-time streaming data with Amazon Kinesis. To add analytics to your app, use:
amplify add analytics
The CLI will guide you through the configuration process, such as providing an analytics service (Pinpoint or Kinesis) and setting up the necessary parameters.
Deploying the app
Once you've set up all the services, it's time to deploy the app. Amplify CLI makes it easy to push your local backend to the cloud:
amplify push
This command deploys all the local backend resources that you've set up with the amplify add commands to the AWS cloud.
Conclusion
AWS Amplify provides a straightforward, unified approach to building full-stack applications. This guide has shown you how to use Amplify to create a full-stack app that leverages AWS Auth, API, Storage, and Analytics services.
Remember that while AWS Amplify simplifies a lot of the work, you'll need to have a strong understanding of AWS services and serverless applications to handle more advanced use cases and effectively troubleshoot issues that may arise.