Wishlist open-source apps

Published: Nov 19, 2024 by Isaac Johnson

With Thanksgiving around the corner and Black-Friday emails flooding our inboxes, I thought I might check out some self-hosted open-source wish list apps.

We’ll set up and use Wishlist as well as Christmas Community and demonstrate their usage. We’ll also take a look at some decent hosted SaaS options as well.

Wishlist

For those wishing they had a nice self-hosted option for sharing wishlists that isn’t tied to some major retailer, Wishlist is worth checking out.

Let’s start with the Docker container first.

I’ll clone the repo and create a default .env file

builder@LuiGi:~/Workspaces$ git clone https://github.com/cmintey/wishlist.git
Cloning into 'wishlist'...
remote: Enumerating objects: 3821, done.
remote: Counting objects: 100% (1040/1040), done.
remote: Compressing objects: 100% (459/459), done.
remote: Total 3821 (delta 675), reused 735 (delta 566), pack-reused 2781 (from 1)
Receiving objects: 100% (3821/3821), 4.66 MiB | 881.00 KiB/s, done.
Resolving deltas: 100% (2085/2085), done.
builder@LuiGi:~/Workspaces$ cd wishlist/
builder@LuiGi:~/Workspaces/wishlist$ cp .env.example .env
builder@LuiGi:~/Workspaces/wishlist$ vi .env
builder@LuiGi:~/Workspaces/wishlist$ cat .env
# If behind a reverse proxy, set this to your domain
# i.e. https://wishlist.your-domain.org
ORIGIN=
# Hours until signup and password reset tokens expire
TOKEN_TIME=72
# The currency to use when a product search does not return a currency
DEFAULT_CURRENCY=USD

then I can docker compose up

builder@LuiGi:~/Workspaces/wishlist$ docker compose up
[+] Running 18/18
 ✔ app 17 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿]      0B/0B      Pulled                                                      129.0s
   ✔ a480a496ba95 Already exists                                                                                   0.0s
   ✔ e28f673e04b0 Pull complete                                                                                    0.5s
   ✔ c737a44a97c3 Pull complete                                                                                   67.4s
   ✔ 9f58814a2227 Pull complete                                                                                    1.3s
   ✔ b658bf75a5fb Pull complete                                                                                    1.1s
   ✔ 55d88dbd8fba Pull complete                                                                                    1.7s
   ✔ af681cf4541f Pull complete                                                                                   76.2s
   ✔ a750316261aa Pull complete                                                                                    2.1s
   ✔ c2d0deb96343 Pull complete                                                                                    2.6s
   ✔ 7721b64b7474 Pull complete                                                                                   21.1s
   ✔ dcbec1c1fafd Pull complete                                                                                   24.1s
   ✔ ed38be4ab95f Pull complete                                                                                  121.6s
   ✔ d6beba06d742 Pull complete                                                                                   68.3s
   ✔ 29522d2270e1 Pull complete                                                                                   68.8s
   ✔ 8dbeb448d174 Pull complete                                                                                   69.3s
   ✔ 19ddcfdbfd36 Pull complete                                                                                   79.3s
   ✔ 4f4fb700ef54 Pull complete                                                                                   76.6s
[+] Running 2/2
 ✔ Network wishlist_default  Created                                                                               0.1s
 ✔ Container wishlist-app    Created                                                                               0.4s
Attaching to wishlist-app
wishlist-app  | {"level":"info","ts":1731623735.5402846,"msg":"using config from file","file":"/usr/src/app/Caddyfile"}
wishlist-app  | {"level":"info","ts":1731623735.5428708,"msg":"adapted config to JSON","adapter":"caddyfile"}
wishlist-app  | {"level":"warn","ts":1731623735.542908,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/usr/src/app/Caddyfile","line":2}
wishlist-app  | {"level":"warn","ts":1731623735.5432708,"logger":"admin","msg":"admin endpoint disabled"}
wishlist-app  | {"level":"info","ts":1731623735.5437567,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0006a1c00"}
wishlist-app  | {"level":"info","ts":1731623735.5438497,"logger":"http.auto_https","msg":"automatic HTTPS is completely disabled for server","server_name":"srv0"}
wishlist-app  | {"level":"info","ts":1731623735.5452514,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
wishlist-app  | {"level":"info","ts":1731623735.5458977,"msg":"autosaved config (load with --resume flag)","file":"/root/.config/caddy/autosave.json"}
wishlist-app  | {"level":"info","ts":1731623735.5459216,"msg":"serving initial configuration"}
wishlist-app  | Successfully started Caddy (pid=18) - Caddy is running in the background
wishlist-app  | {"level":"info","ts":1731623735.552621,"logger":"tls","msg":"cleaning storage unit","storage":"FileStorage:/root/.local/share/caddy"}
wishlist-app  | {"level":"info","ts":1731623735.5529034,"logger":"tls","msg":"finished cleaning storage units"}
wishlist-app  | Prisma schema loaded from prisma/schema.prisma
wishlist-app  | Datasource "db": SQLite database "prod.db" at "file:/usr/src/app/data/prod.db?connection_limit=1"
wishlist-app  |
wishlist-app  | SQLite database prod.db created at file:/usr/src/app/data/prod.db?connection_limit=1
wishlist-app  |
wishlist-app  | 26 migrations found in prisma/migrations
wishlist-app  |
wishlist-app  | Applying migration `20221229151102_init`
wishlist-app  | Applying migration `20221229153655_signup_token`
wishlist-app  | Applying migration `20221229183508_email_required`
wishlist-app  | Applying migration `20230116175745_item_approval`
wishlist-app  | Applying migration `20230201201336_lucia_key_0_5_0`
wishlist-app  | Applying migration `20230203145115_system_config`
wishlist-app  | Applying migration `20230221220353_add_item_purchased`
wishlist-app  | Applying migration `20230306173006_add_key_expires_in`
wishlist-app  | Applying migration `20230306211437_add_profile_picture`
wishlist-app  | Applying migration `20230411190603_groups`
wishlist-app  | Applying migration `20230411193602_lucia_1_0`
wishlist-app  | Applying migration `20230811191732_lucia_v2`
wishlist-app  | Applying migration `20231011193501_token_uuid`
wishlist-app  | Applying migration `20231017150829_remove_expires_in`
wishlist-app  | Applying migration `20231022025934_add_default_currency_symbols`
wishlist-app  | Applying migration `20231128030637_add_claim_show_name_config_option`
wishlist-app  | Applying migration `20240101035724_add_referential_actions`
wishlist-app  | Applying migration `20240118052807_fix_initial_config_generation`
wishlist-app  | Applying migration `20240330005940_lucia_v3_update_session_expires_at`
wishlist-app  | Applying migration `20240330011054_lucia_v3_keys_removal`
wishlist-app  | Applying migration `20240330024912_standardize_session_casing`
wishlist-app  | Applying migration `20240330035425_add_indexes_and_standardize_db_naming`
wishlist-app  | Applying migration `20240618010500_add_public_lists`
wishlist-app  | Applying migration `20240625011332_add_system_user_for_anonymous_claims`
wishlist-app  | Applying migration `20240825030505_add_display_order`
wishlist-app  | Applying migration `20240902211258_add_price_id`
wishlist-app  |
wishlist-app  | The following migration(s) have been applied:
wishlist-app  |
wishlist-app  | migrations/
wishlist-app  |   └─ 20221229151102_init/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20221229153655_signup_token/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20221229183508_email_required/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230116175745_item_approval/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230201201336_lucia_key_0_5_0/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230203145115_system_config/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230221220353_add_item_purchased/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230306173006_add_key_expires_in/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230306211437_add_profile_picture/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230411190603_groups/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230411193602_lucia_1_0/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20230811191732_lucia_v2/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20231011193501_token_uuid/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20231017150829_remove_expires_in/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20231022025934_add_default_currency_symbols/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20231128030637_add_claim_show_name_config_option/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240101035724_add_referential_actions/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240118052807_fix_initial_config_generation/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240330005940_lucia_v3_update_session_expires_at/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240330011054_lucia_v3_keys_removal/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240330024912_standardize_session_casing/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240330035425_add_indexes_and_standardize_db_naming/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240618010500_add_public_lists/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240625011332_add_system_user_for_anonymous_claims/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240825030505_add_display_order/
wishlist-app  |     └─ migration.sql
wishlist-app  |   └─ 20240902211258_add_price_id/
wishlist-app  |     └─ migration.sql
wishlist-app  |
wishlist-app  | All migrations have been successfully applied.
wishlist-app  | Running seed command `node prisma/seed.js` ...
wishlist-app  | roles are synced
wishlist-app  | created default group
wishlist-app  | Patching item price model
wishlist-app  | 0 items to update
wishlist-app  | Finished patching item price model
wishlist-app  |
wishlist-app  | 🌱  The seed command has been executed.
wishlist-app  |
wishlist-app  | > wishlist@0.0.1 start /usr/src/app
wishlist-app  | > node build
wishlist-app  |
wishlist-app  | Listening on 0.0.0.0:3000

However, it did not serve traffic

/content/images/2024/11/wishlist-01.png

I double checked the compose file and it actually is serving up on 3280

/content/images/2024/11/wishlist-02.png

I created a user with password, then moved to the next step

/content/images/2024/11/wishlist-03.png

The SMTP was optional and not required, but I decided to try it as well as auto-approve on suggestions

/content/images/2024/11/wishlist-04.png

The last step is to (optionally) invite users

/content/images/2024/11/wishlist-05.png

Now i have a wish list

/content/images/2024/11/wishlist-06.png

I can add a Wishlist item

/content/images/2024/11/wishlist-07.png

I now have a nice listing for a very nice fish finder

/content/images/2024/11/wishlist-08.png

Though, it would just be a private list for me at this point as the app requires login

/content/images/2024/11/wishlist-09.png

I can easily signup, but it doesn’t let me see other users Wishlists

/content/images/2024/11/wishlist-10.png

As admin in my primary account I can add a user

/content/images/2024/11/wishlist-11.png

Once that happened, my second user could see my lists.

I tried the password reset

/content/images/2024/11/wishlist-13.png

And soon I saw the reset email

/content/images/2024/11/wishlist-14.png

Which did actually properly redirect

/content/images/2024/11/wishlist-15.png

Kubernetes

Let’s start by converting this to a Kubernetes Manifest

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: uploads-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wishlist-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wishlist-app
  template:
    metadata:
      labels:
        app: wishlist-app
    spec:
      containers:
        - name: wishlist-app
          image: ghcr.io/cmintey/wishlist
          ports:
            - containerPort: 3280
          env:
            - name: ORIGIN
              value: "https://wishlist.freshbrewed.science"
            - name: TOKEN_TIME
              value: "72"
            - name: DEFAULT_CURRENCY
              value: "USD"
          volumeMounts:
            - mountPath: /usr/src/app/uploads
              name: uploads
            - mountPath: /usr/src/app/data
              name: data
      volumes:
        - name: uploads
          persistentVolumeClaim:
            claimName: uploads-pvc
        - name: data
          persistentVolumeClaim:
            claimName: data-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: wishlist-app-service
spec:
  selector:
    app: wishlist-app
  ports:
    - protocol: TCP
      port: 3280
      targetPort: 3280

Then apply

$ kubectl apply -f ./wishlist.yaml
persistentvolumeclaim/uploads-pvc created
persistentvolumeclaim/data-pvc created
deployment.apps/wishlist-app-deployment created
service/wishlist-app-service created

Now I’ll create the ingress, but first I need an A record

$ cat ./r53-wishlist.json
{
  "Comment": "CREATE wishlist fb.s A record ",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "wishlist.freshbrewed.science",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "75.73.224.240"
          }
        ]
      }
    }
  ]
}

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-wishlist.json
{
    "ChangeInfo": {
        "Id": "/change/C09827583PKJGXTOTSTJN",
        "Status": "PENDING",
        "SubmittedAt": "2024-11-15T02:49:38.250000+00:00",
        "Comment": "CREATE wishlist fb.s A record "
    }
}

Now an ingress to use it

$ cat wishlist.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  name: wishlist
spec:
  rules:
  - host: wishlist.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: wishlist-app-service
            port:
              number: 3280
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - wishlist.freshbrewed.science
    secretName: wishlist-tls

$ kubectl apply -f ./wishlist.ingress.yaml
ingress.networking.k8s.io/wishlist created

Once the cert came up

$ kubectl get cert wishlist-tls
NAME           READY   SECRET         AGE
wishlist-tls   True    wishlist-tls   2m20s

I can now start the setup

/content/images/2024/11/wishlist-16.png

I’ll create an account

/content/images/2024/11/wishlist-17.png

Once my account is setup, I’ll add an icon

/content/images/2024/11/wishlist-18.png

Let’s try using it

Christmas Community

I can test it

builder@LuiGi:~/Workspaces/christmas-community$ docker compose up
[+] Running 1/1
 ✔ Container christmas-community-christmas-community-1  R...                                                       0.2s
Attaching to christmas-community-1
christmas-community-1  |
christmas-community-1  | > christmas-community@1.36.0 start
christmas-community-1  | > node built/manager.js
christmas-community-1  |
christmas-community-1  | [ EXPRESS ] Express server started on port 80!
christmas-community-1  | [ DB EXPOSE ] DB has been exposed on port 8080
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /setup
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /libraries/bulmaswatch/default/bulmaswatch.min.css
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /libraries/fontawesome/css/all.css
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /libraries/animate.min.css
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /js/nav.js
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /css/main.css
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /img/logo.transparent.png
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /libraries/fontawesome/webfonts/fa-solid-900.woff2
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /img/logo.transparent.png
christmas-community-1  | [ EXPRESS ] ::ffff:172.23.0.1 - GET /manifest.json

I can test the login

/content/images/2024/11/christmascommunity-01.png

and saw a landing page

/content/images/2024/11/christmascommunity-02.png

I can add the same Garmin

/content/images/2024/11/christmascommunity-03.png

but no icon on listing, just a new entry

/content/images/2024/11/christmascommunity-04.png

Let’s convert this to a Kubernetes Manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: christmas-community-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: christmas-community
  template:
    metadata:
      labels:
        app: christmas-community
    spec:
      containers:
        - name: christmas-community
          image: wingysam/christmas-community
          env:
            - name: NODE_ENV
              value: production
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: christmas-community-service
spec:
  selector:
    app: christmas-community
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 80

Let’s apply

$ kubectl apply -f ./christmas.yaml
deployment.apps/christmas-community-deployment created
service/christmas-community-service created

Let’s create an A Record in AWS

$ cat r53-christmas.json
{
  "Comment": "CREATE christmas fb.s A record ",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "christmas.freshbrewed.science",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "75.73.224.240"
          }
        ]
      }
    }
  ]
}

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-christmas.json
{
    "ChangeInfo": {
        "Id": "/change/C0045276KG6394M6AO7S",
        "Status": "PENDING",
        "SubmittedAt": "2024-11-15T02:13:39.490000+00:00",
        "Comment": "CREATE christmas fb.s A record "
    }
}

Now let’s create an ingress

$ cat christmas.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  name: christmas
spec:
  rules:
  - host: christmas.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: christmas-community-service
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - christmas.freshbrewed.science
    secretName: christmas-tls

$ kubectl apply -f ./christmas.ingress.yaml
ingress.networking.k8s.io/christmas created

I then can connect and setup an Admin user

/content/images/2024/11/christmascommunity-05.png

Once I created a user

/content/images/2024/11/christmascommunity-06.png

I then dded a watch

/content/images/2024/11/christmascommunity-07.png

Let’s try using it

SaaS offerings

I figured it was worth comparing to some simple hosted options we can easily find

Happy Wishlist

The first result I found was HappyWishlist

/content/images/2024/11/happywishlist-01.png

I pick a landing URL

/content/images/2024/11/happywishlist-02.png

Then can login with Google/Apple or create a fresh account

/content/images/2024/11/happywishlist-03.png

The MFA suggested the real backend is ‘throne.me’, I might add.

I next pick a country

/content/images/2024/11/happywishlist-04.png

And lastly set a public name and icon

/content/images/2024/11/happywishlist-05.png

Now we can create a list

GiftList

Another result that came up and even automatically prompted for federated login was giftlist.com

/content/images/2024/11/giftlist-01.png

I did the sign-up with Google

/content/images/2024/11/giftlist-02.png

Note: this MFA showed it was a Firebase hosted app (meaning GCP)

/content/images/2024/11/giftlist-03.png

Once I verified age, I could start to make lists

/content/images/2024/11/giftlist-04.png

And using it

More

There are so many more SaaS sites, and they are all very similar

And let’s not forget we can always use Target or Amazon to track our lists

/content/images/2024/11/amazonlist-01.png

Or Target

/content/images/2024/11/targetlist-01.png

Summary

Today we explored two easy-to-use Gift list apps that are self-hosted, wishlist and the older but still very good Christmas Community. We fully set them up in Kubernetes and compared features before rolling to looking at some easy-to-use SaaS offerings. In fact, I just touched on the wide range of Gift Registry sites who all look to make a bit off referral links. Lastly, I just did a quick look at Amazon and Target.

I think the only issue I have with a mega-retailer like Target or Amazon is you get rather locked in on their offerings. This is fine for mainstream items, but what if I want a particular board game and they don’t list it

/content/images/2024/11/targetlist-02.png

Or the Amazon price is a bit high compared to other retailers like Cabela’s (BassProShops)

/content/images/2024/11/amazonlist-02.png

Lastly, I think I just sort of soured on the concept; perhaps that is just me and my age. I don’t really want anything from others. I don’t mind a list for myself for those occasional out of the blue tax returns or bonuses, but I would feel rather weird someone dropped 3G on a trolling motor for me - so why list it?

I found my Amazon wishlist is an interesting stream of historical desires I had. It does show the passions of the seasons I have had over time (with the general theme of eclectic music, camping and fishing - and now boating.. for fishing).

In the end, I might suggest a simple hosted option would fit most peoples needs - and assume it could go away in time. If I had to think of long term solutions with family I might consider a shared Google Doc or just a backend blog entry. Perhaps the next blog on this should just focus on lists in general. Let me know what you think.

OpenSource Lists Wishlist ChristmasCommunity Kubernetes Docker

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes