Conversation

I started a new app recently, and while I've used Laravel in the past for "heavy" apps, decided to go 100% with Next.js this time, as a challenge. As I'm building all the scaffolding from the app, I've borrowed some ideas from Laravel that JS devs might find interesting... 👇
23
442
While I mostly use for the Queue these days (no other solution comes close, especially coupled with Vapor), you usually don't think about the work it does for you: 📨 Crafting & sending mail 🔐 Authentication & authorization With , you start from scratch. 😅
4
36
For authentication, you usually want to use next-auth.js.org. Howeverrr, their DB schema is quite complex, and I like going around the database manually, so I decided to try my hand at it. Can I build a "magic link" email login flow, with just a User table? 🤔
Database schema required by NextAuth.js, which composes four tables ("User", "Account", "Session", and "VerificationToken").
2
29
This is the schema I want to use (using cuz it seems like the cleanest ORM for TypeScript, missing Eloquent tho 😅). You'll notice it doesn't include any session data or verification tokens (which you'd need for the magic login links). So, how did I make it work? 😁
A screenshot showing a Prisma schema for a User table, with "id", "name", "email", "createdAt", "updatedAt" columns.
3
25
First, let's cover the session. I'm using 's iron-session package, which lets you store a small (encrypted) object inside a cookie. 🍪 I use this to store the ID of the user (and the current team as well in my case), and then fetch it from the database when I need them.
import prisma from '@/lib/prisma'

const handler = async (req, res) => {
	if (!req.session.userId) {
		return res.json({ authenticated: false })   
	}

	return res.json({
		authenticated: true,
		user: await prisma.user.findUnique({
			where: { id: req.session.userId }
        }),
	})
}

export default handler
2
32
Finally, what about the verification token? Here's where I'm taking a page from the Laravel playground, recreating their "signed links" feature. I'm encrypting an object (w/ Iron) with the user's email and an expiry date and using that as the token users get on their email! 🤩
import Iron from '@hapi/iron'
import { sendMagicLink } from '@/lib/mail'
const BASE_VERIFY_URL = "https://scripty.studio/api/auth/login"


const handler = async (req, res) => {
	if (!req.body.email) return res.status(400).end()

	const token = await Iron.seal({
		email: req.body.email,
		expires: now().add(1, 'hours').toDate()
    }, process.env.APP_KEY, Iron.defaults)

	await sendMagicLink(
		req.body.email,
		`${BASE_VERIFY_URL}?email=${req.body.email}&token=${token}`,   
	})

	res.status(200).json({ message: 'Email sent!' })
}
import Iron from '@hapi/iron'

const handler = async (req, res) => {
	const { email, token } = req.query
	if (!email || !token) return res.status(400).end()

	const loginData = await Iron.unseal(
		token as string, process.env.APP_KEY, Iron.defaults
    )

	if (email != loginData.email || loginData.expires < now().toDate()) {   
        // expired link (or someone is trying to swap the email lmeow)
		return res.status(400).end()
	}

	const user = await prisma.user.findUnique({ where: { email } })
	if (!user) return res.status(400).end()

	req.session.userId = user.id
	await req.session.save()

	res.redirect('/dashboard')
}
1
28
Note this has the downside that links will work for as long as they are active, so I'd recommend to set it to a short period (I'm using 1h). The upside is a much much cleaner database, and clean code I really feel like I understand (which is huge!) 😁
1
19
As a bonus note, let's talk about how I'm crafting and sending emails! 📬 I write my emails in JSX using mjml-react (which comes with a pretty nifty VSCode extension w/ live-previews). When it's time to send, you can render to html from node, and send it using nodemailer. 🚀
Image
Image
3
35
And that's it! I'll keep tweeting lil DX hacks for making Next more manageable for big apps (at least from the perspective of someone used to MVC & lots of magic), so follow if you're into that i guess. Also, check out the app I'm building! I think it's pretty dope 😁 ✌️
Quote Tweet
Been working on a lil script-writing app 👀 it feels really crazy how fast I can go from "i wish this existed" to a prototype, coding really is a magical skill ✨ scripty.vercel.app
Show this thread
Image
Image
Image
Replying to
wait so what are you using as a replacement for the missing Queues? i clicked into this thread hoping for insight there haha
1
1
Replying to
oh you're not the first person saying this, may have made the thread clickbaity accidentally 😅 still working on that! i really don't like running them over HTTP, so been thinking about making a package that deploys some lambdas and sets up SQS as part of the build process
1
1
Show replies
Replying to
Nice work! I ran into something similar a few months back when building one of the center.dev frontends. Next.js is really nice, but what ended up really burning time was getting next-auth to work with django auth (which we're using to power some backend things)
1
1
Show replies
Replying to
Setting up persistence & authentication on Next.js is really messy. Had to code a mongoose adapter. I'm hitting the database twice per sign in as a workaround to modifying the user model. On the other hand, it's joy 'cause I got to see what my code is doing. No magic.
1