Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Creating a newsletter manager with Turso and Qwik
James Sinkala
James Sinkala

Posted on • Edited on • Originally published atMedium

     

Creating a newsletter manager with Turso and Qwik

A newsletter is an email marketing strategy tool used by individuals, publishers, organisations, businesses to share valuable information and engaging content with their customers, helping in promoting sales and driving traffic to their websites.

In this post I’m going to demonstrate how to create a newsletter usingTurso, the edge database based onlibSQL - the open-source and open-contribution fork of SQLite, andQwik, the resumable, instant-on applications framework being built by the folks at builder.io.

Before getting to the building part, I’d like to share a couple of reasons as to why I’m working on this and using the selected technology.

Newsletter platforms are either unreliable or expensive, here’s why I rolled my own

My go-to option for my own newsletters had been Revue, butTwitter recently shut that down, and others like Ghost and ConvertKit are quite expensive for my liking, so I did some research into building my own so you don’t have to.

I have been following Qwik, a resumable, instant-on applications framework, and I love the philosophy behind it, so it was a no brainer to build with. I paired it with Turso, a DBaaS (Database-as-a-service) that has the best DX (Developer Experience) I’ve come across — which is one of the reasons I ended up joining the company, full disclosure — for the speed and low latency it can provide alongside Qwik.

Creating the newsletter manager with Turso and Qwik

The newsletter manager we are creating has three pages.

The first page is one that subscribers interact with when subscribing, which includes a brief introduction to what the newsletter is all about, and a form.

The second, a page accessible to the newsletter’s editor which enables them to view the subscribers emails, blogs they subscribed to(if multiple), and the subscription dates.

The third page is the unsubscribing page. Like any good newsletter, you’d want to give your subscribers both the option to opt-into and opt-out of it.

Ideally, the second page would involve authentication of some sort as it should warrant limited access, but we won’t be covering authentication in this post.

The source code to the project we are working on in this post is also availablein this GitHub repository.

Prerequisites:

Setting up the Turso database

Instructions on the installation of Turso, database creation, and getting the Turso database URL can be found on theproject’s initialization instructions on the GitHub repository.

You’ll need the Turso database URL obtained after creating a database as that is needed inside our application.

The Qwik app

As discussed, and as you'll notice by observing the source code, the project is built using the Qwik framework, and we are using thelibSQL's client for TypeScript (@libsql/client to perform queries on our database. With this, we can work with local SQLite file databases seamlessly just like using our Turso databases on the edge.

Something that we are going to be seeing repeatedly in Qwik code is the $ symbol. Let it not confuse you, Qwik uses it to signify the extraction of code to both the developer andQwik optimizer. Qwik splits up the application into many smaller pieces dubbed "symbols" using the Qwik Optimizer. More on this can be read inthe Qwik docs.

Let's cover the most interesting bits of the newsletter manager.

The newsletter home page

Inside the home page, we are updating the loading state and calling thesubscribeToNewsletter() function when the form is submitted<form onSubmit$>.

useSignal() anduseStore() are two Qwik hooks you’ll find throughout the pages used to declare reactive state.useSignal() takes an initial value and returns a reactive signal consisting of an object with a single.value property, while theuseStore() hook takes an object as its initial value and returns a reactive object.

Theserver function in Qwik — server$ lets us work in the server layer of the application right next to our client code.Here is more information on it, andhere is a demonstration.

subscribeToNewsletter()inside the home page is a server function that submits the subscription information to the Turso database and updates us on the subscription state.

import{createClient}from"@libsql/client";exportconstsubscribeToNewsletter=server$(async(email:string,newsletter:string)=>{if(!email||!newsletter){return{success:false,message:"Email cannot be empty!",};}constdb=createClient({url:import.meta.env.VITE_DB_URL,});awaitdb.execute("insert into newsletters(email, website) values(?, ?)",[email,newsletter,]);constresponse=awaitdb.execute("select * from newsletters where email = ? and website = ?",[email,newsletterBlog]);constsubscriber=responseDataAdapter(response);returnsubscriber[0]?{success:true,message:"You've been subscribed!",}:{success:false,message:"Sorry something isn't right, please retry!",};});exportdefaultcomponent$(()=>{constemail=useSignal("");constloading=useSignal(false);constemailRegex=/\b[\w.-]+@[\w.-]+\.\w{2,4}\b/gi;constnotification=useStore({message:"",status:"",});return(<formpreventdefault:submitonSubmit$={async()=>{if(!emailRegex.test(email.value)){alert("Email not valid!");return;}loading.value=true;constresponse=awaitsubscribeToNewsletter(email.value,newsletterBlog);loading.value=false;notification.message=response.message;notification.status=response.success?"success":"error";}}><inputonInput$={(e)=>{email.value=(e.targetasHTMLInputElement).value}}name="email"type="email"/><buttontype="submit">Subscribe</button></form>);});
Enter fullscreen modeExit fullscreen mode

Qwik also supports SEO (Search Engine Optimisation). To set it up on pages, we export a head object of theDocumentHead type with HTML header metadata. We can see this inside each of the three pages, thehome,admin, andunsubscription pages.

Here’s a preview of the home page.

Newsletter page preview

The newsletter admin page

In the component UI of thenewsletter admin page, we are using Qwik’sResource component which renders its children when the passed resource is resolved, and renders a fallback when it is pending or rejected.

In Qwik, resources are created using theuseResource$ method. We can see a demonstration of a resource by looking at thesubscribersResource needed by the<Resource ... /> component referenced above.

constsubscriberRows=(subscribers:NewsletterSubscriber[])=>{returnsubscribers?.length<1?(<tr><tdcolSpan={4}>        No subscribers found</td></tr>):(subscribers?.map((sub:NewsletterSubscriber,index:number)=>{return(<trkey={sub.id}><td>{index+1}</td><td>{sub.email}</td><td>{sub.website}</td><td>{formatDate(sub.created_at)}</td></tr>);}));};exportdefaultcomponent$(()=>{constsubscribersResource=useResource$<ResourceResponse>(async()=>{constresponse=awaitdb.execute('select * from newsletters');constsubscribers=response?.success?responseDataAdapter(response):[];return{message:"Fetched subscribers",data:subscribers,};});return(<div><h1>Newsletter Admin</h1><Resourcevalue={subscribersResource}onRejected={()=><Notymessage="Failed to fetch subscribers"type="error"/>}onPending={()=><LoadingAnimation/>}onResolved={(res:ResourceResponse)=><table><thead><tr><th>No.</th><th>Email</th><th>Website</th><th>Joined</th></tr></thead><tbody>{subscriberRows(res.data)}</tbody></table>}></Resource></div>);});
Enter fullscreen modeExit fullscreen mode

After adding some subscribers, we should see the table populated with their information when visiting this page.

Newsletter admin page

The newsletter unsubscription page

Normally unsubscription links should be accessible from within the newsletter emails. To unsubscribe users, we need to pass parameters to the unsubscription route.

Qwik’s file-based routing is similar to other frameworks, in that we pass route parameters declaring them as directory names inside square brackets.

To pass the domain and email parameters, we’ve set up the directory structure for the unsubscription page as follows.

└── routes    ├── unsubscribe    │   └──[email]    │       └──[domain]    │           └── index.tsx
Enter fullscreen modeExit fullscreen mode

We then use Qwik’suseLocation() function to retrieve aRouteLocation object for the active location from which we acquire the email and domain parameters.We use these two parameters to delete the user’s data from the database, to unsubscribe them from the newsletter inside another server function.

The whole unsubscription process is automated by making sure thatunsubscribeFromNewsletter(), which is also a Qwik server$ function, is triggered when the page component is rendered on the browser. We do this by calling this function inside theuseBrowserVisibleTask() hook.

import{createClient}from"@libsql/client";exportdefaultcomponent$(()=>{constlocation=useLocation();constemail=useSignal(location.params.email);constdomain=useSignal(location.params.domain);constloading=useSignal(false);constnotification=useStore({message:"",status:"",});constunsubscribeFromNewsletter=server$(async()=>{constdb=createClient({url:import.meta.env.VITE_DB_URL,});constdeleteRecord=awaitdb.execute("delete from newsletters where email = ? and website like ?",[email.value,domain.value]);if(!deleteRecord.success){return{success:false,message:"Sorry, something isn't right, please reload the page!",};}return{success:true,message:"Unsubscribed!",};});useBrowserVisibleTask$(async()=>{loading.value=true;constres=awaitunsubscribeFromNewsletter();notification.message=res.message;notification.status=res.success?"success":"error";loading.value=false;});});
Enter fullscreen modeExit fullscreen mode

Here is a demonstration of what would happen when unsubscribing users, starting inside the mail view.

Unsubscribing users

That’s it for the newsletter manager.Visit the project’s repo on GitHub for the source code and to learn more about it.

To learn more about Tursoclick here to visit the documentation, and as for Qwik, you can readthe official documentation over here.

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
pengeszikra profile image
Peter Vivo
Pipeline operator and touch bar fanatic from Hungary.God speed you!
  • Location
    Pomaz
  • Education
    streetwise
  • Work
    full stack developer at TCS
  • Joined

At last I found which type is the target in onInput$ handler function! Thx!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Full-Stack Developer | Technical writer. Based on Earth, The Milky Way.
  • Location
    Dar es Salaam
  • Joined

More fromJames Sinkala

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp