Loading video player...
Hey friends, today I want to show you a
new way of working with data in SWKID
and that is remote functions which are
currently experimental. So use them at
your own risk. But let me tell you the
truth, remote functions are so enjoyable
and fun to use that I honestly don't
care. Basically, if you ever use TRPC,
then you know what remote functions are.
So remote functions basically let you
invoke functions from the server on the
client like their regular functions. And
under the hood, they're just a fetch
request. All right, so let me give you a
taste of remote functions. Let me show
the URL bar because this is going to be
important. So usually in So the URL
segment dictates where you're loading
the data. So for example, in this case
where you're on the homepage, usually
this is going to be a load function. So
you would have to create a plus
page.server.ts file. And if you go to
the post, you would also have a load
function here. So you would have to
create another file. And the same is
true for this form. So you would use
form actions for something like this.
And let me think deep about some of my
favorite comments on YouTube. So of
course that's going to be swelt is react
now banger comment.
Let's send. Okay. So now we can also say
view did it first. That is also one of
my favorite comments. And we can just
post it. As you can see it works
beautifully. But of course if we go here
to the admin section here we can
actually control the posts. In this
case, we can have many different authors
that each have their own posts. And in
this example here, we have a layout. So
this is a nested layout. So on the left,
we can just list the author's post. So
again, we would need a load function.
And then if we go create this post,
again we will need to use form actions.
So we can say remote function slap
and we can say this is a post about
remote functions. Awesome. Let's publish
it. And you're going to see it works as
expected. And now we can go back here
and we can look at the post. And if you
don't like the post, no problem. We can
go back to the admin and we can just
delete the post. But in this example,
we're using zero load functions and
instead we're taking advantage of remote
functions. So if I open VS Code, you can
see that there are no plus page.serverts
files. Here we're just using a regular
function. And thanks to a synchronous
swelt, we can just use it straight in
our template. And that is all possible
because I have it enabled in the swel
config. As you can see under compiler
options here, you have async true. And
for swell kit, it's important that we
have remote functions enabled. And
that's it. Now we can invoke functions
from the server as regular functions on
the client. So for example, if I go here
to get post, you can see this is a
special remote.ts file. And on this
line, we're querying the post from the
database. So going back to our example,
we can open the developer tools. And if
you go to the network tab, at first if
you refresh everything, you're not going
to see anything special because the
content gets server side rendered. So if
you for example open local host, you're
going to see in the preview here, we get
everything. So that's really great. But
if you for example go to the admin
section, so now the site is going to use
client side navigation. So if we go back
to swells, you're going to see it's
going to load our posts. And if you
click on the post, we're going to see
that this is just some good
old-fashioned JSON. And in the headers,
we can see this is just a regular API
endpoint. And this is the advantage of
something like this being baked into
Swellit because the developer experience
is so much better compared to something
like TRPC. Of course, this also means
this endpoints are public. But from what
I understand, Sulki doesn't allow
someone to make cross-sight requests.
And we're also going to look at how we
can validate the input so you can't
submit junk. All right, friends. I
wanted this tutorial to be as real as
possible. So I'm using better off for
the authentication and I'm using Drizzle
as the ORM. And of course, it would take
forever if we did this entire project
from scratch. So I included all this
examples here on the main branch. But if
you want to follow along in this
tutorial, I'm going to show you how you
can set everything up. So you can start
from this starter branch where I deleted
all of the remote functions code. so you
can follow along with me and learn
remote functions. So let me show you how
you can quickly set up this project. All
right, here is the setup if you want to
follow along. So first we're going to
say npx deit then the name of the repo
pound starter. So this is going to be
the name of the branch and then you can
name the folder whatever you want. So
instead of example I'm going to name it
remote functions and then press enter.
And sometimes there's this weird thing
where deit hangs for example. So you can
just press Ctrl C if that happens to
you. But it basically just takes a
second. Awesome. So now we can cd into
remote functions and now we can list the
contents and let's say ls- a so you can
see hidden files. So now we have to
rename the environment file. So for
example, if you say cat environment
example, you're going to see here the
environment variables. So you don't have
to set anything up and don't worry about
this better o secret. This is just
autogenerated junk from their tutorial.
So everything is going to work fine for
you. All right. So we just need to
rename environment.example to
environment. So we can say move
environment.example to env. All right.
So now if we look at our files, you're
going to see it's renamed properly. So
if we cat env.
All right. So now you can install your
dependencies and you can use npm, pnpm,
whatever you want. It doesn't matter.
I'm going to use pnpm because it's
faster and it should take a second.
Awesome. So let's create the tables in
our database from the Drizzle schema. So
you can say pnpm and you can say run db
push and then it's going to ask you,
hey, do you want to create these tables?
Yes, I want to execute these statements.
Boom. That's it. Now we're done. And now
we just need to start the development
server.
All right. Now you can open the project
in your favorite code editor. As you can
see, it's mostly placeholder data. And
if you go to localhost 5173, everything
should be working. And there's one more
cool thing that I want to show you. So
in this example, we're using SQLite. So
we don't have to worry about setting up
a database. And as you can see, I can
even open it inside VS Code and that is
thanks to this SQLite viewer extension.
So you can install this if you want and
it's really awesome in my opinion. So
you can get SQLite viewer by Florian
Clair and you can just install it and
you can open an SQLite database inside
VS Code. Alternatively, if you want, you
can also use Drizzle Studio, which is a
fantastic graphical user interface for
interacting with your database. All
right, friends, let's take a brief look
at remote functions before we start
using them. Most of the time, you're
only going to use two remote functions.
So, the first one is going to be the
query remote function. So, this is used
to read dynamic data from the server,
such as this example where we read post
from a database. And this is just a
promise. So you can use it directly in
your template thanks to a synchronous
swelt. But of course you can also use it
at the top level of the script block.
And there is even an alternative syntax
if you want to use something like tan
stack query. So we can just use this
promise here. Keep in mind we're not
awaiting it. We're just assigning to
query. And now we get access to the
error loading and current fields on the
query itself. But in this video we're
mostly going to use the await form. And
of course you can also pass arguments to
the query which you're going to do for
the post. And then you have validation
using any standard schema library which
is really awesome when we talk about
forms. So you can also batch queries in
this example. So this solves the n plus1
problem. So here we're looping over the
cities and we want to get the weather.
This would be multiple requests. But if
you want to make a single request you
can use query.batch.
So instead of a city being passed here
as an argument, now we get cities. So
this becomes a single query. And this is
not something that you're going to use
often, but keep that in mind if you ever
need it. But the most exciting remote
function to me is the form function
because you can pass in any standard
validation schema. So you can use zod
valbot and so on. And as you can see
now, you no longer need to do that form
data dance using some utility or a third
party library. We can just pass the
schema here and based on the schema now
we get back the title and content field
and now you can do whatever you want and
I absolutely love this because this is
so much less bureaucracy than before and
if you don't need a form you can use a
command function. So let's say for
example that you want to like a post
which is something that you're going to
do well in that case you might not need
a form so you can use a command instead.
So this is a simple way to write some
data to the server. So just like in this
example, as you can see, we can just
define this add like function.
And now we can invoke it by clicking a
button. That being said, most of the
time you're probably going to want to
use a form because you get progressive
enhancement out of the box. So your form
is going to work even when JavaScript is
disabled for some reason.
And if you want to pre-render content
such as blog post, you can use the
pre-render function. And this is very
exciting because now you can have
pre-rendered and dynamic content on the
same page. So it's not all or nothing.
And you have other things here such as
how to handle validation errors. But one
thing that is more important to us is
get request event. The beauty of remote
functions is in the name functions. That
means you can compose them like regular
functions. So inside the query form and
command function, we can use get request
event to get the current request object.
So we can see if the user is
authenticated or not. So we can just
create a simple authentication function
which we can reuse across our queries.
All right, enough talking. Let's see how
we can use remote functions.
All right, let's start by creating a
simple function to fetch all of the
posts. The beauty of remote functions is
that you can create them anywhere. They
can be inside your routes, collocated
alongside your components or anywhere
else where you want them. But in this
case, I'm just going to go to the li
folder and I'm going to create a new
folder called API. This is irrelevant.
You can place them anywhere you want.
And I'm just going to name this post
remote.ts. And this is the only
important part. This file name has to
end with remote.ts orjs if you're using
regular JavaScript. And that's it. So
now we can create this file. Now all of
our post logic is going to be located
inside API/post.ts.
All right. So let's create the get post
query. So you can say export const get
post equal query which is imported from
swell kit and we can just pass an async
call back. All right. So now we can use
drizzle to get the post. So here is how
we do that. We can say await and then we
can say db. So we can import the drizzle
client as you can see here. Nothing
special. And then of course to select
the post you can say select and in this
case I'm going to import all of the
tables. So I'm going to say import all
as table from
lib
server
database and it's going to be schema I
think. So if I go here you're going to
see here I have just all of my tables.
So here's a user table. If I go to the
bottom you can see here I have comments
post and etc. So yeah that's basically
it. All right. So we can go here and
then we can just say from and we can use
the table we want. So we want table.post
post and then we can return the post. Of
course, we don't have any posts yet
here. So, let's create another query
which is going to be get offer post and
this is also going to be our guarded
query which we can use in the layout and
then we can redirect the user if they
try to go to the admin section if
they're not logged in. So, let's just
say export const get offer post. Then we
can say query
and we can also make this async if you
want. All right. So to get the offer
post, we actually have to know the offer
ID. And that is only possible if we know
the information about the loggedin offer
or user. And to do this, we can create a
simple off function which we can reuse
across all of our remote functions. And
it's simple like this. So let me show
you. So we can say function require off
or you can name this whatever you want.
So now we can use get request event to
get the information from the locals. And
if you don't know what I'm talking
about, we can go to hooks for example.
And this is how I have better off setup.
And this is most of the solutions you're
going to encounter when you're
implementing off inkit. So here we're
just using this better off await API get
session then you're checking hey if
there's a session then we can just set
the localals to this session and the
user. So now anywhere where we're
requesting an event in circuit we're
going to have this data. And if this is
confusing let me know in the comments if
you want a dedicated better off video.
All right. Going back to our code, we
can just say const and we can the
structure locals
from get request event which is going to
be also imported from cellit. All right.
So now we can just check the locals for
the user. So we can say hey if there's
no user signed in then we can just
redirect. So we can import redirect from
cellit then let's say 307
and then we can redirect to our slash
off/lo
route. And in case the user is logged
in, we can just return the user data. So
you can say locals user and as you can
see this is already typed because in
app.ts I included all of this
information. So I imported the types
from server database schema. And this is
really simple if you're using drizzle.
So we can just infer the type from the
table. That's basically it. All right.
So now we can make this guarded query.
So we can say const user equals require
of. So we're going to get the user
information and anywhere we're using
this query since we're checking if the
user is logged in, we're going to
redirect if they're not. So this is how
you make a guarded query. And of course
if this isn't enough for you, you can
use hooks. Server TS and you can
actually check the route here. And then
if you have an entire protected route
and if you want to be more sure, you can
just put a conditional here and you're
only going to check for that route. So
you can always use hooks.ts instead. All
right. But in this case, we just want to
return the offer post. So now we have a
bunch of things on this user like ID and
etc. So we can say conpost equals await
db select
from we can say table.post
and we can say where. So we can use this
equals function from drizzle.
And of course we can say table post
offer ID where it matches the user ID.
So we're going to return only the offers
post. Awesome. Now we can return the
post. And that's it. As you can see that
is how simple authentication is. So
whatever route includes this remote
function with require off is going to
automatically become guarded. And if you
use it inside of a layout then the
entire route becomes protected. So be
careful when you're using it in nested
routes because if you only use it in one
part of those nested routes, that isn't
enough. You either have to use the
function in the layout of that entire
route or you can use hooks.
All right. And in all of my excitement,
I didn't even register this error. So
what does this say? Property users does
not exist on type local. Did you mean
user? Why yes, of course. Oh, my bad.
This is just user. There we go. All
right. So let's go to the homepage. So
we can go to source/ routes. And now we
can fetch all of the posts here. So
let's remove this placeholder. We can
use await at the top level of the script
block or inside of the template. So we
can say await get post. And that's it.
How beautiful is this? And now instead
of slug here, we can say post. And check
this out. This is all typed of course.
So we can say post slug. And then
instead of title, we can say post title.
That's it. But of course, we don't have
any posts yet. So next let's work on the
admin section. All right. So we can go
to the dashboard and this is the route
that we want to protect. If the user
isn't authenticated we want to redirect
them to off slash login and if they
don't have an account they can go to the
signup page. But as you can see here in
admin this is just a simple dashboard
with a nested layout. So if we go back
to our code we can see admin here. It
has two simple routes create and edit.
And if we open pluspage.tilt We can see
here is the admin text and the welcome
message with a sign out option. And here
we have our main plus layout.swelt. So
this is what we want to modify. As you
can see right now this is just a
placeholder and this is a perfect place
to use our guarded remote function. So
we can go here and remove this. We can
say await offer post and that's it. So
now we can go here we can remove this
placeholder. You can say post log. And
we can do the same for the title.
We can say post title.
All right. And now since you're not
logged in, if we go back to our homepage
and we can see no post found, which is
great. So let's say that someone
stumbles upon admin. Oops. They can't go
here anymore. As you can see, that was
really simple. And because we use the
guarded query in the layout itself, the
entire route should be protected. All
right. So let's take care of the login,
sign up, and the logout functionality.
So we can go back to our code. And now
we're going to work in this section off.
So here we have a login and signup
route. Nothing special. Here we have a
simple form. Now we just need to create
a remote function.
All right. So going back here in our
sidebar, let's create a new file which
I'm going to name off remote TS. And
this is going to be responsible
everything off related.
All right. So first let's create the
signup function. So we can say export
const sign up. And now we can use the
new form query from cellit. And as you
can see this is really interesting. By
default it requires you to pass a
validation schema. But if you're
prototyping or have your own validation
you can just pass unchecked. And then
you're going to get the data from the
form. In our case this is going to be
user. And we can even make this async.
But in this case I'm going to import a
schema I already made. So I'm going to
start typing signup schema and this is
going to be imported from lib/s
schema/off. And if you go to the schema,
you're going to see it just uses zod.
But you can use any validation library
that supports the standard schema. So
you can use z valuebot and so on. And
it's just going to work. As you can see
here, we have this name, email, and
password fields. And now that we
validated the data, this is completely
typed. You don't have to do that form
data dance or use some utility function.
it just works. So in this example, we
can use the better off to register the
user. So we can say await and we're
going to use their server API. So we can
import off from lip server off. And if I
go here, you can see it's just that
simple. This is just their client and
then we can say off API
signup email and then we have to pass
this body. So let's see what it accepts.
We can see the accepts email name
password and since the user already
matches the shape we can just pass user.
That's it. And now we can redirect the
user. And let me just import this from
cell kit. And we can say 307. And now
that the user is registered, we can
redirect them to admin.
So let's say slashadmin. And of course
you can also return whatever you want
from this form. So you can return banana
and you can have whatever here and then
on the client you can say from this
function sign up dobbanana. You can
check for something whatever you want
and that's how that works. But in this
case we're just going to create two more
functions. So we have sign up and let's
create login.
So this is going to use the login
schema. Again we're going to get the
user and we can get their information
from the request. So we can say const
request
equals get request event from cirkit.
And now instead of signup email we can
say sign in by email. So we have to pass
the same value in the body which is the
user and this also requires a header. So
we can say headers and we can get it
from the request. So we can say request
headers. That's it. And this is going to
be 303. And we can also redirect to
admin. All right. So let's also
implement logout.
Let me just copy this over. So let's
name this sign out.
In this case we don't require any
schema. So we can just omit this part.
And we don't have anything here. All
right. So now we can remove this line
and we can just sign out the user. So we
can say of API
sign out and then we can just pass
what's required. So in this case it
requires scatters. So we can say request
headers.
There we go. And we can redirect the
user to the homepage. And that's pretty
much it for the validation when it comes
to forms. And I'm also going to create a
function to get the user data. Better
off has an API for that, but it's
honestly kind of broken. I don't know.
It's really simple to do your own. So we
can just say export const get user. This
is going to be a query. We can make this
async even though I don't think it's
required.
All right. So, we can just get the user
information if they're logged in. So,
again, we can get locals from get
request event.
And then we can actually do the same
check. So, if there isn't anyone logged
in, we can just redirect.
We can say 307 and then we can say off
login and then if they are logged in, we
can just return their information. So,
we can say locals user. Boom. There we
go. Let's do a bit of formatting.
And honestly, that's it. That's how
simple that was. All right, let me show
you how cool this is. So, let's go to
the login route. We're going to open
plus pagesweld. And now, the only thing
that we have to do is spread login on
the form. So, we can import login. Let's
import it. And that's it. So now this is
going to spread all of the required
attributes on the form. And not only
that, but this is progressively enhanced
out of the box. So whenever it can, it's
going to use JavaScript by default. But
if JavaScript isn't available for some
reason, it's going to still work. So if
we go to our login page here, let's open
the developer tools. And now we can
inspect the form. And you're going to
see all of the required attributes are
on the form, such as the method and the
action. So you don't have to think about
that. But it gets even cooler. In this
case, we can treat this like a regular
form. We can just pass the type
ourselves and the name, and that's going
to be fine. If we go back to our
function, this is where we're going to
receive it. Here we have the user. So we
have all of the fields, email, password,
and so on. But SWELKit makes this even
more type- safe. Instead of doing this,
we can actually spread the fields. So
let me show you how that looks like. So
we can remove this and we can use the
curly voice. Let's say login fields. Now
we can pick the field that we need,
which is going to be email. And we need
to tell it what type it is. So we can
say as email. And this is really cool.
And now we can also show the issues by
just looping over them. So let's use the
each block and we can say login
fields
and we can pick the field that you want
to show the issues. There can be
multiple issues for some fields. So we
can loop over them. So we can just say
issues and we can invoke that. But of
course this can be undefined. So let's
specify a default value which is going
to be an empty array and then let's say
as issue.
All right. So now you can just create a
paragraph tag. We can say issue and then
we can say issue dossage. That is how
simple that is. Of course we can repeat
the same for the password field. So let
me just take this for sake of
convenience.
So we can just spread the type here.
Instead of email this is going to be
password
and the type is also going to be
password. So, we can also copy and paste
this code
and instead of the field being email,
it's going to be password. And of
course, I forgot a curly boy here. All
right. So, now if I save and format,
everything should look great.
All right. So, let's do the same thing
for the signup. So, we can go to sign
up. Let's open that. And we can do the
same thing. So, we can use the curly
boys. Let's spread sign up.
That's it. So now we can do the same
thing to the fields. We have username,
email password.
All right, let's do that quickly. So we
can just spread sign up fields. We can
pick the field. It's going to be name
as. Now we can pick the type which is
going to be text.
And let's loop over the issues. So we
can say sign up
fields name issues. We need to invoke
this. And again let's set the default
value and we can say issue. Let's create
a paragraph tag with an issue class and
then we can say issue dossage. All
right. So let's actually copy this for
all of these fields.
This is going to be email.
Let's copy the issues. And again you can
probably make a snippet or a component
out of this
if you find this tedious. So we can also
say email.
Let's also copy over this.
It's going to be password.
And let's copy over the issues.
All right, let's format and save. And
this should be it.
All right, now we should be able to
register and log the dashboard. All
right, so back on the login page, let's
sign up for an account. So we can enter
a name, whatever you want. Let's enter
our bogus email, example.com. I'm going
to copy it as the password. Don't tell
anyone. Let's sign up. And we're going
to see everything works. Now we're
logged in as admin. All right. So let's
display the user information and let
them sign out. So we can go back to our
code editor. Let's start typing admin.
And it's going to immediately give us
the plus page.l file. So let's open
this. And now we can use our get user
function.
So we can say con user equals we can use
a weight directly here and we can say
get user which is going to be imported
from lip/ ai/off remote. And that's it.
This is how simple it is to create your
own functions. We don't need to rely on
some library. And of course in this case
instead of hard coding name
we can just say user.name
and for the signup we just need to
import sign out and spread it here.
So we can do that and that's it. So now
you can save this and you can see here
is our name and let's see if we can sign
out the user and awesome now it works.
It redirected us. So now we can see if
we go to soil tricks there's no post.
Let's go back to the dashboard. We have
to log in. So, we have to provide our
which is example.com
and don't tell anyone about my email.
Let's see if we can log back in.
Beautiful. All right. So, now we can
focus on actually creating the post. All
right. So, let's go back to the post
remote.ts file. And here we're going to
create two remote queries. We're going
to create one for creating the post,
updating the post, and deleting the
post. So let's say export con create
post
and then we can say form.
So this is going to use the create post
schema and as you can see on this schema
we have the title slug and the content.
Nothing special. All right. So let's
make this async and we're going to get
back a post.
Of course we need the user information.
So we can use require off. So we get the
user information such as the ID and then
we can say away DB and then we can
insert this value into post. So we have
to say table.post
and the values we want to insert. So as
you can see here are the values that we
have to pass. All right. So now we can
spread the value it already expects
which is going to come from the post. So
we're going to do that and then we can
pass in the offer ID. So that is going
to come from user ID. All right. So
after we added the post, I want to
redirect to the editing view. So we can
just use a redirect. So let's say
redirect from silkit. We can say 303.
And now let's use back ticks. So we can
say /dadmin /edit slash the post slug.
And that's it. So now we can copy this
over. We're not going to reuse a lot of
it. So we can just rename create post to
update post.
The schema is going to be update post
schema.
So you can see on it we have id, title,
slug and content. And here I'm going to
destructure the values that we need. So
instead of post let's say ID, title,
slug and content. This is going to be
easier to pass to drizzle. And we can
actually delete all of this inside of
here. All right. So let's say await db
update. We want to update the post.
So we can say set. And now we can just
pass the values. So we can say title,
slug and content.
And then we can say where we can use eq
from drizzle. All right. So we can say
where table post ID is equal to the ID
that we passed. And that's it for
updating the post. So we can actually
copy this over.
So instead of updating the post, we're
going to say remove post. And this is
going to share the same schema because
it's going to be the same form with a
different action. And instead of passing
all of these values, we only need the
ID. So let's remove these values.
And let me remove this line. So we can
say away DB delete
table post
where. And then we can use equals and we
can say table post ID equals to the ID
that we passed. All right. Right. And
then we just want to redirect.
So we're going to say free and we want
to redirect to /admin. And that's pretty
much it. These are all of the queries
that we need for creating, updating, and
deleting posts. All right. So if we
start typing create, we should see plus
page for the route that we need. So we
can just open this and we can see this
should be the create new post form. So
let's do the same thing as we did
before. So we just need to import and
spread create post.
So you can see it's imported for us at
the top. And now we can just spread the
fields and loop over the issues. So
let's go here
and we can spread create post
fields. We're going to pick the title as
is going to be of type text. All right.
So now let's look over the issues. So we
can say create post
fields title issues. Let's invoke it and
we can just say issue. All right. So
let's create a paragraph with issue and
then we can just say issue message. All
right. So let's also set a default value
here. And then we can copy this over.
And yeah, it would be probably easier to
copy the entire block, but whatever. And
then instead of title, this is going to
be slug. Let's just copy over these
issues.
This is also going to be slug. And
beautiful. Now we have this content here
which is just a text area. So let's
again copy over this part.
No big deal.
And let's copy over the issues.
Boom. That's it. And we can also
optionally show a loading indicator. So
since I'm using pico CSS, I can just use
the area busy attribute. So for example,
I can say area busy and then we can set
it to the pending value. So we can say
create post. This is the query pending.
But it's not happy with this because
this isn't a boolean. This is going to
be a number. So how many of these
queries are going to be pending? What we
actually want to just check, hey, if
there's any pending query. So we can
turn this into a boolean. And the
easiest way to do that is using the
double exclamation mark. And this is
going to happen too quick. So let's
actually add a delay on this query. So
we're recreating the post. We can just
say await. And let's import delay. This
is a helper function I have from utils.
And then let's set it to 300
milliseconds. And if you're curious,
this is just a simple promise. As you
can see, it just returns a promise that
we resolve after a certain duration. All
right. So let's create a post. So here
we have our create new post form. So we
can say example
post and then we can give it some bad
slug. For example, let's see if our
validation works and then let's say for
content example post. All right. So if
you say publish, you're going to see we
have an error here because the
validation works thankfully. So we can
make this a proper slug. And then we can
publish it. And as you can see it's
going to redirect to the edit route. But
of course, we only get these placeholder
values because we haven't done anything
with this form yet. So, let's do that.
All right. Before we go to the edit
route, I'm just going to copy and paste
this delay here to this other query so
we can show the loading spinner. And
that's pretty much it. So, now we can
start typing edit and we can find plus
page.swelt. So, we can open it here and
we can see this is just a simple
placeholder and this is just a simple
form with a title slug content. And here
we have a hidden input. So we can send
the post ID and we also have two actions
here. One for updating and one for
deleting. So we're also going to see how
that works. All right. So let's think
about what we want. So when we go to the
edit route /dispose log, we actually
want to fetch the post information
including the ID so we know what post to
update. So how can we do that? Well, we
need to create some query. In this
example, we're going to create a get
post query.
So back in our editor, let's go back to
post remote.ts. DS and now we can create
our get post query somewhere here. So
after we refetch post
so we can say export
const get post and this is going to be a
simple query and we can also reuse this
query when we go to the post slug in the
other route. And this query also accepts
a slug param. So we can just create a
simple schema. If I press Z, we can
import Z from Zod mini and let's just
say Z string. So you don't need any
fancy schema. And then we're going to
get back the slug.
All right. So we can say conpose
equals await db select
from table
post. And now we can say where equals.
So we can say table post slug is equal
to the slug that we passed.
And then let's return post. And now I
just want to show you what is being
returned. All right. So the first thing
we need to do is get access to the
params. So we can destructure params
from props.
So here is how that looks like. So let's
just log it in this way. So you can say
params.
So now if you go to the example post,
you can see here we get this log example
post based on the URL. All right. So now
we can use this to fetch the post. So we
can come here and we can say con post
equals we can use derived because we
want to update this post each time
params updates. So we can say await get
post which we're going to import and
then you can just pass params slug and
it's really interesting it's using dojs
for this. I think it's really important.
I don't know why I did something goofy
like that. And we're going to get rid of
this in a second but let's just say post
and then we can log it here. So I'm
going to show you what it gives us. All
right. And as you can see now we get
this array from drizzle. But in our case
we're only interested in the first
result. So let's go back to post remote
and then we can just the structure the
array and we can just get the first
post. That's it. And now we can return
this. And let's also use a check here.
So we can say if there isn't a post we
can use an error from cellit. So let's
import error then we can say 404
post not found. And that's pretty much
it. So now we can see even if you
refresh we're going to get the single
post from Grizzle. All right. So now
going back to our code, we can now
remove this placeholder since we no
longer need it.
And this is no longer post. This is
singular post. And now we can remove our
helper here. All right. So now when we
go to our example, you can see it
already works. So let's say for example,
if you go to sell tricks back to admin
here, you're on the admin page. Now when
you go to edit the post, you're going to
see the fields are populated.
All right. So let's update the form by
importing update post and spreading it
on the form. All right. So now we're
going to do the same thing as before,
but in this case we're actually going to
leave this value here alone because we
actually want these default values to be
present. But here we can just spread
update post
and we can say fields title as text. And
let's loop over the issues
update post.
fields title issues
as issue and now we can go here we can
say p issue not is issue interesting we
can say issue message all right so let's
repeat this for the slug
practice makes perfect right all right
so we can go here leave the value as is
this is going to be slug and it's going
to be text all right so we also need the
issues All
right. So, let's see what else do we
have here. We have content. Same as
before. All right. Let's take this
and let's paste it here. So, this is
going to be content and let's copy the
issues.
That's it. All right. So the hidden
field is a bit more interesting.
In this case, instead of using value,
we're going to use a more type- safe
API. So we're going to spread update
post
fields ID. And now we can say s and the
first argument is going to be hidden.
And now we can pass post ID.
All right. So now we're going to get
type safety because it's complaining
that this doesn't expect a number. So we
actually have to convert this to a
string. And we can do this in many
different ways, but I'm just going to
say two string. And that's it. All
right. So, let's add a loading spinner.
All right. So, let's also add area busy.
And we're going to say update post
pending. Let's do the same thing as
before. Now, we can copy this over to
delete.
But we also want a different action
here. So, to do this, we can spread a
different query. So, we can say remove
post. And we're also going to import it.
And then we can say button props.
All right. So let's see if we can update
the post. Let's add an exclamation mark
and then let's hit update. And as you
can see it works, but Swellit reset our
form which is really interesting. That's
not what JavaScript does by default. But
SWKit tries to emulate the default
behavior of forms. Unfortunately in this
example, this works against us because
you can see if I refresh the page,
everything works as expected. All right.
So how can we fix that? Well, you can
actually control the behavior of the
form. So, if I go here where we have
update post, we can go here and we can
use this enhance method. So, we can
invoke it and we can pass a callback
function. So, let's see what do we get
here by default. So, by default, we get
the data form and the submit method. And
this is actually what we want. We
actually want to use JavaScript, but
it's not going to reset the form by
default. So, we can just invoke submit
here. And later we're also going to look
at how we can use enhance for optimistic
UI updates. But in this case, let's just
save. And now when we go here and update
the post. Let's see if it works. Hit
update. And as you can see now the form
doesn't reset. And also if we go to
swell tricks to our homepage, we're
going to see now we see our posts. And
if you go back to admin to our post,
let's try deleting the post.
And it works as expected.
All right. Let's also fix a couple of
issues I noticed. So, we didn't even
specify a default value here. So, let's
do that quickly. We're just going to set
this as an empty array. And I think we
also need to do this for the content
here. Awesome. Let's see if there's
anything else wrong here. I noticed
something weird when we deleted the
post. So, this is remove post. Okay. All
right. This isn't update post. This
should be remove post. So, let's
actually see if we create a new post.
Let's just say test.
Now we should see our loading spinner
here. We press publish. Let's say if we
update test.
We also see a spinner here. And we
should see a spinner when we delete it.
All right, we fixed that. But in a real
project, you probably want to set a
minimum duration before you show a
loading spinner because if it happens
too fast, then it's just going to flash
and it's going to look janky. All right.
So the last thing that we have to do is
actually show the post when you navigate
to it. So for example, if we create a
post titled remote function slap and
let's also create a slug remote function
slap.
This is a post about why remote
functions slap.
All right. So let's publish it. As you
can see everything works as expected.
When you go to soil tricks, you can see
our post here. But when we go to the
post itself, this is just using
placeholder content. All right, so let's
work on showing the post, liking the
post, and leaving a comment.
All right, so back in the editor, we can
navigate to slug. So we can go to
source/ route/lug. And this is going to
be very similar to the admin route. All
right, so first let's replace this
placeholder content. So we're going to
the structure params from props
and then we're going to create this post
variable. We're going to use a derive.
So this post updates when the slug does.
So we can use a way directly inside. We
can say get post. Let's import this. And
then we can pass params slug. And that
should be it. So we can even use our old
trick here. Let's log post. So we're
going to see we get our post. All right.
So we can remove this and instead of
title,
let's say post title. And here you can
use markdown if you want, but I'm just
going to say add HTML post content. All
right. So if we save this and we look at
our post now, we're going to get the
content. All right. So let's see if we
can get more easy wins. We're going to
leave the liking the post and comments
for later. But I think that we can just
use post here. All right. So we can go
here. We can say await get post. And
that's it. Let's import that. And now we
can replace these placeholders.
We can say post slug and then we can
also replace the title with post.title
and we can see that it works. All right.
So let's go back to our code now and now
we're going to work on liking the post.
So let's go to post remote.ts
and let's find a cozy spot here.
All right. So first we need to get the
post likes which also means that we need
to pass the post ID. So let's create a
get post likes query. So we can say
export const get post likes and we can
just use a query and let's just say Z
number
and then we also get access to the ID
and that's pretty much it.
All right. So we can say con slice
because Drizzle is going to again return
an array. So let's just say away DB and
let's make it more legible because this
is going to be a longer one. So we can
say select and in this case we're only
going to select the field that we're
interested in which is going to be
likes. So we can say likes and then we
need to pick it from the table. So we
can say table post likes
and then we can say from table
post and then we have to say where. So
we can say equals
table post id is equal to the id that we
passed and then we can return the likes.
So we can say return likes and if this
is undefined then we're going to return
zero. So let me actually show you this.
So here where we have our likes we can
just go here. Let's create a new line.
So we can say await. And now we can
import get post likes and then we can
pass the post ID. But this isn't going
to work because of course this is going
to return some array and we can even log
this. So let me actually see we can do
the same trick here. All right. So let's
do this. And you can see it returns this
array with likes. So we just want to
return likes and nothing else. So let's
actually go back to post remote. And
then we can do the same trick as before.
We can destructure these likes by using
curly boys and then we can just say
likes. All right. So now if we go back
here, you're going to see we only get
the likes back. Awesome. So let's add
liking the post. And for this we're
going to use a command because we don't
need a form in this case. But of course
you can use a form if you want. And this
is what commands are useful for in cases
where you don't require a form but you
want to write some data to the server.
And keep in mind the benefits of forms
is progressive enhancement. So they're
going to work regardless if you have
JavaScript or not. All right, but in
this case, let's just say export const.
And then we can say like post and this
is going to be a command. So let's
import that. And this is going to be a
number. And we also get an id.
So we can say away db and this is also
going to be lengthy. So let me use a new
line. So we can say update. We want to
update post. And then we want to set the
likes in this case. And we have to use
this special SQL function from Drizzle.
So it makes it easy to just increment
this field. So we can use back tick
and then we say table post likes
and we can say plus one. So we're just
doing a raw SQL query. And then you have
to say where
equals
table post id the id that we passed. All
right. So now let's actually use this
query. So let's remove this and we can
go here. So let's add on click
and let's import like post and of course
we need to pass in the post ID. All
right. So let's save everything and
let's see if it works. Let's refresh for
good measure. And you're going to see
nothing happens. And that's because we
have to be explicit when we're using
commands what queries need to refresh.
By default, when you're using a form
query and it does a successful
submission, all of the queries are going
to update. But in this case, we have to
specify that manually. And you can even
use the same trick on other queries.
So let's actually go back here. And
here's what you have to do. So what is
the query that we want to update? It's
get post likes. So we can pass in the ID
and then we can invoke this special
refresh method so that SWKid knows to
invalidate this query. All right. So we
can save and you already see if we
refresh the page, we get the data back.
But now let's see if we can like the
post.
As you can see, it works as expected.
All right. So let me show you how we can
do optimistic UI updates. And if you
don't know what it is, let's say, for
example, that it takes a while to like
this post. So, for some reason, maybe
your server is slow. I don't know. So,
let's say await delay. And this can be 2
seconds. So, let's say, for example,
that the user is on this page and they
try to like a post. And now it looks
like nothing is happening. They're
getting frustrated. They click like and
they're like, "Oh, what is going on? Let
me just click a thousand times." Right?
You've been in that situation probably.
In this case, we can use optimistic UI
updates, which is using JavaScript to
fake that this happened instantly on the
client. So, you can do this trick with
commands and forms. And if it fails, we
can revert to an older value. And let me
show you how easy this is with remote
functions.
So, back in our code editor, let's go to
our post. And now, here where we have
this query, let me put it on a new line
so it's more readable. We can use this
special method updates. And let me also
make more space so it's readable. And
now we can specify the query that is
going to update by liking posts. So we
can say get post likes. We can pass the
post ID same as before. But now we can
actually use this special method where
we can override the result. So this is
going to temporarily override it. So we
can say with override and now we get
access to the result which is going to
be likes in this case. So the only thing
that you have to do in this simple
example is say likes plus one. That's
it. All right. So now let me save and
format everything. And now remember we
still have a 2cond delay. We didn't
change anything but we're using
optimistic UI update so it appears
instant. So let's go back to our example
and let's see what happens when you like
the post. As you can see it happens
instantly.
How beautiful is this friends? This is
the power of optimistic UI updates. And
if this looks ugly to you, keep in mind
these are just functions. You can just
abstract this. The beauty of remote
functions is in the name. They're just
functions. So you can compose them like
regular functions. All right. So the
last thing that you have to do is add
the ability to fetch comments and post
comments. So inside post remote ts, we
can just create our two queries. All
right. So let's say export const get
post comments and we're also going to
require the post ID to get the
appropriate comments. So we can say
query and we can say that this is going
to be a number and of course we get
access to the ID.
All right. So let's say cons comments
equals await db
select
from table dot comments.
Then we can say where equals table
comments post ID is equal to the ID that
you pass. And we can just return
comments. In this case, we're not going
to use authentication for the comments
or anything like that. But maybe you can
do that as an exercise. All right. So we
just need to add the ability to post
comments. So we can say export const
post post comment. And of course this is
going to be a form query. And we can use
the post comment schema.
So this is going to give us the post ID,
offer, and comment. And we can make this
async. We're going to get back the
comment. So we can insert it into the
database. And let's say await db
insert in the comments table. And we can
just insert the values which is going to
be the comment. And let's also add a
delay here. So we get a spinner. So we
can say await delay 300 milliseconds.
And that's pretty much it. All right. So
let's go back to the comments markup.
And here we have this placeholder.
So now we can say await get post
comments. So we can import that. And of
course we need to pass in the post ID.
And now we can replace these placeholder
values.
And you can also put an at here. It
doesn't do anything special. It's just
decorative. All right. So now where we
have the form we just have to do the
same thing as we done before we can just
import and spread post comment and then
we can leave this default value here
which is going to be anonymous. So we
can just spread the field here post
comment
fields offer as text
and we can loop over the issues. So
let's say post comment fields offer
issues
as issue and let's also use a default
value. Awesome. So now let's say p issue
and we can say issue dossage.
All right. So let's copy over this issue
here and we're going to copy this field.
So this is going to be a comment
and let's say comment
and comment here also.
And this is going to be the same thing
as before.
Let's spread post comment
fields post ID
as we're going to make this hidden and
we need to pass post ID. But of course
this type doesn't match because we need
to turn this into a string. Awesome. And
then let's add a spinner here. So we can
say area busy.
And then we can say post comment
pending. All right. So now back to our
post. Let's see if this actually works.
So we can leave a comment as an
anonymous user. We can say hi. Let's
post the comment. We're going to see our
spinner and the comment is going to be
left. Awesome. But there's one problem.
You notice that the field reset. So we
have the same problem as before. But in
this case I want to keep the name field
and I want to reset the comment. So that
is actually very simple to do.
So here where we have the form again we
can use the method. We get a call back
and now we can use submit again and it's
not method my bad is enhance
and we can just invoke submit but there
is also another API that we can use. So
we can get the values and set them using
this special swellit API. So in this
case we can say post comment fields
comment and then we can set it. So we
can just reset the field and that's
pretty much it. All right. So a bit of
formatting and let's actually look at
it. So we can see anonymous. Let's say
test. We can pause the comment. As you
can see the comment field clears but we
leave the anonymous name.
All right, friends. We're almost done.
There is just one more thing I want to
talk about. You're probably wondering
with remote functions, when would you
create a dedicated API route? Well,
there's actually two cases. So, one case
is where you want to expose your remote
function. So, let's say for example that
you want to expose this post to the
entire world where you can create a
dedicated API endpoint using plus
server.ts. So in this example, we're
just importing get post and then we're
fetching them inside of this get
function and then we're returning them
as JSON. So for example, if I go here to
the website and I go to / API /ost,
you're going to see we get back a JSON
response. And of course, we don't have a
lot of post, but as you can see, it
works. So this is one example where you
would create a dedicated API endpoint.
Another example is when you need a
custom response such as an RSS feed. So
here in RSS.xml in this plus.s server.ts
file, we're importing get post and then
we're creating this RSS feed. So we get
the post and when we're done with all of
this, we can just return an XML response
with the appropriate headers.
So again, now if we go to the API and we
go to RSS now, we're going to see since
I'm using Valdi, it's going to use its
built-in RSS reader. But I don't want to
blind you. So let's go back. All right,
friends. So, I hope that makes sense.
And that's basically it. I also want to
remind you that remote functions are
experimental and you can expect breaking
changes. It seems like the majority of
the remote functions API is locked down,
but there's still some experimentation
going on with things like forms. So,
based on this pull request, it seems
like button props are going to be
removed in favor of this improved more
type- safe API. So if you have multiple
actions on a form now you no longer need
to create multiple form queries you just
need one. So instead of saying register
dotbutton props so you spread the same
query and say dot fields doaction submit
and then you type the action. So in this
case this is register. So now instead of
having this extra register function
where you have this login form now you
have this payload.action.
So now we can check what action the user
submitted and then you can do whatever
you want. And the SW team is also
cooking on some exciting features such
as streaming live data from the server.
So expect changes and make sure to read
the documentation. All right, that's it.
If you like what you seen, don't forget
to like and subscribe and I'll catch you
in the next one. Peace.
Learn how to work with data in SvelteKit using remote functions. š“ Patreon: https://www.patreon.com/joyofcode š¬ Discord: https://discord.com/invite/k6ZpwAKwwZ āļø Links š Remote Functions: https://svelte.dev/docs/kit/remote-functions š GitHub: https://github.com/joysofcode/sveltekit-remote-functions š SQLite Viewer: https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer š Timestamps 0:00 Intro 0:30 Remote Functions Example 3:45 Project Setup 6:32 Remote Function Types 9:45 Using The Query Function For Posts 11:26 Creating A Guarded Query For Author Posts 15:28 Looping Over Posts 16:05 Guarding A Protected Route 17:27 Creating The Form Functions To Authenticate The User 22:13 Using The Login Form Function 24:39 Using The Signup Form Function 26:37 Displaying The Authed User Information And Using The Signout Form Function 27:53 Creating Form Functions For Creating, Updating, And Deleting Posts 31:02 Creating Posts Using The Form Function 34:04 Updating And Removing Posts Using Form Functions 42:19 Showing Posts Using The Query Function 44:15 Using A Query To Get Post Likes 46:26 Using A Command Function For Liking Posts 48:54 Optimistic UI Updates 51:00 Using Remote Functions To Fetch And Post Comments 55:39 When To Use API Routes 57:06 The Future Of Remote Functions 58:06 Outro #joyofcode #sveltekit #svelte