Skip to main content

Command Palette

Search for a command to run...

Glimpz: The Ultimate Music Discovery App | AWS Amplify Hackathon

Updated
7 min read
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}")
}
💡
Note: In this article, all APIs are publicly accessible for simplicity and demonstration purposes (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.