sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.69k stars 1.93k forks source link

Is it possible to add custom netlify functions #1249

Closed loweisz closed 3 years ago

loweisz commented 3 years ago

Hey there 👋

I'm trying to deploy my svelte kit app to netlify, which seems to be working perfectly, when using the adapter.

However, I also wanted to deploy a custom function of mine and I noticed that this function is never deployed to netlify. Only the function called render.

I also noticed that when I add my custom function to the specified functions directory and then run npm run build, my custom function is removed from the functions folder and only the render function is still there.

I'm wondering am I missing something here or is it currently not possible to add a custom function to netlify when using the adapter ?

Thanks in advance 🙇‍♂️

benmccann commented 3 years ago

Can you just create a SvelteKit endpoint?

This may also be somewhat a duplicate of https://github.com/sveltejs/kit/issues/930 where existing Netlify config is overwritten

asgerb commented 3 years ago

Maybe I can chime in as I've been bitten by this as well.

Perhaps it's due to my lack of understanding the intricacies of SvelteKit, vite, or node more specifically, but I wasn't able to figure out how to use a Svelte endpoint since I needed to use puppeteer and the require syntax wasn't working (to my understanding it's due to it being node syntax and not esm). This might be due to vite though, more than SvelteKit...

Apologies for the fuzzy details, I could spin up the app and provide a more detailed summary if that's helpful?

benmccann commented 3 years ago

@asgerb your issue seems unrelated to this ticket and would probably be best handled on Discord

loweisz commented 3 years ago

@benmccann Sorry for the late response.

Yes with SvelteKit endpoints it works perfectly, thanks for the tip. However, I would still say it might be a confusing behavior when trying to use netlify functions directly. But I could solve my use case without 👍

ClaytonFarr commented 3 years ago

@benmccann I think this is still a real issue and need.

There is unique Netlify functionality that doesn't seem accessible via SvelteKit endpoints yet.

Two examples I'm currently encountering are -

  1. Not able to take advantage of specially named functions that trigger on events (https://docs.netlify.com/functions/trigger-on-events)
  2. Not able to access user context information from Netlify authentication product 'Identity'; this is needed to be able to perform admin actions (https://docs.netlify.com/functions/functions-and-identity)

I'm not sure if / when it'll make sense for SvelteKit to afford this ability via endpoints (esp if #2 is unique to Netlify only).

But, being able to create and include additional serverless functions in the build somehow (e.g. copying files from an aliased directory into the 'build/functions' directory) should make this work.

Without it, I'm finding myself running in circles trying to implement a full auth solution using Netlify.

If there's a way to work around this in the short, that'd be great to know.

Thanks.

benmccann commented 3 years ago

Interesting. I wonder if perhaps the Netlify adapter should copy context.clientContext into SvelteKit's locals

ClaytonFarr commented 3 years ago

That'd a big help for issue #2 above.

Being able to also add specially named function files would be very nice too. I'm not sure if Netlify can catch these as function names bundled in SKs 'render' function. It doesn't seem like it.

ClaytonFarr commented 3 years ago

Looking forward to this hopefully being achievable natively in the Netlify adapter.

For others who stumble into this need and issue in the meantime, this workaround may be helpful -

https://discord.com/channels/457912077277855764/819723698415599626/846467065196052510

splatte commented 3 years ago

My usecase is similar to @ClaytonFarr's as I want to prevent protected admin pages from rendering unless the user is logged in. I am using patch-package to make this small change to the netlify-adapter https://gist.github.com/splatte/8d57ba18d5f0643119e242beb424a3c3 which lets me access user context information in SvelteKit's handle function:

  netlifyContext: {
    custom: {
      netlify: 'eyJpZGVudGl0eSI6eyJ1cmwiOiJxxxxxxxxxxxxxxxxxIQWlPakUyTWpJeU5UvY29'
    },
    identity: {
      url: 'https://abcdfdfdfdfd.netlify.app/.netlify/identity',
      token: 'eyJhbGciOiJIxxxxxxxxxxxxxxxkpQqdV0bDJVgq562Ac'
    },
    user: { /* only populated when Authorization: Bearer <token> is present in request */ }
  },

However, https://docs.netlify.com/functions/functions-and-identity/ states that "The user object is present if the function request has an Authorization: Bearer header with a valid JWT from the Identity instance. In this case the object will contain the decoded claims." so the header does not seem to be present when accessing the page via the browser. The browser sends the token via the nf_jwt cookie which doesn't get decoded by netlify automatically.

I hope I am on the right track here, but how do I get netlify to decode the claims automatically when the user accesses a protected page like /admin/secret-page?

ClaytonFarr commented 3 years ago

@splatte Nice. I wasn't aware of using an approach like patch-package in the interim; I'll have to try that.

Regarding passing the token as an authorization header, you can parse your cookie in the hooks.js to use it in your function call.

Hooks.js will receive all cookies automatically on each request. You can then parse the JWT and attach it to the request.locals object (including httpOnly cookies) to have it available to the request, to or attach any of the decoded JWT claims to the session.

Here is some example code from a current project - https://gist.github.com/ClaytonFarr/d4555f396179b0375652285abe43d37e

  1. within hooks.js you'll see the parsing of the cookie token and attaching it to request.locals.token
  2. within updateEmail.js you'll see this request.locals.token value being grabbed and passed to a helper method for Netlify Identity (I ended up recreating the GoTrue-JS methods in the auth-api.js file to be able to access an Identity instance directly in serverless functions.)
  3. within the helper method in auth-api.js you'll see this token being attached as an authorization bearer token in the final call.

For cases where you also need an identity admin token from clientContext, you'd either leverage your patch to get this data out of a normal SvelteKit endpoint (if I understand it correctly) – or create a custom serverless function and call this function as a mid-step to get the admin token before hitting the final Netlify Identity endpoint.

splatte commented 3 years ago

awesome, thanks a lot for this great input @ClaytonFarr! I have a lot to go through :)

swyxio commented 3 years ago

@ClaytonFarr i might be able to help... are you able to provide a small sample repo with what you're trying to do?

ClaytonFarr commented 3 years ago

Thanks @sw-yx, that's incredibly kind.

Currently, I'm manually creating functions that either require additional clientContext (e.g. for a Netlify Identity admin token) or that have to be specially named to trigger on events (e.g. identity-signup) and automatically copying these post-build into the functions directory.

Are you thinking there may be a better way to handle this? If so, I can definitely pull together some examples. Thanks again.

swyxio commented 3 years ago

@ClaytonFarr yes i do think that there may be a way to handle this, but also the netlify adapter was probably not created with this usecase in mind so we may want to fix this uptstream. examples would help motivate the discussion.

fwiw i used to work at netlify and helped make netlify dev so thats why im volunteering since my background can probably help here

ClaytonFarr commented 3 years ago

@sw-yx Great, thanks. I should be able to pull together a smaller example that helps illustrates this within the next few days.

ClaytonFarr commented 3 years ago

@sw-yx Sorry for the delay in sharing an example. Was smacked with work for a moment. I'm tying up a few pieces on a project that should be a good example of key use cases that run into this problem. I hope to have that done this week.

ClaytonFarr commented 3 years ago

@sw-yx Here's a repo of an example project that exemplifies the limitations and workarounds I'm currently seeing / implementing -

repo running demo

[Update: realized I was missing a portion of the setup & instructions in the repo / demo above that was preventing new signups from confirming. Should be fixed now.]

This has several moving parts and integrations (Netlify Identity for authentication, Fauna for user data, Stripe for subscription plans and management, Tailwind). The most relevant parts for this are the additional serverless functions that need to be created outside of normal SK endpoints -

1) to access data in context.clientContext (e.g. identity tokens) -

2) to create specially named functions that trigger on events (e.g. post user signup) –

These 'additional_functions' are currently copied post build into the final functions directory via package.json script.

Within the demo you can create / manage / delete users to experience functionality.

Hope this helps. Thanks again Shawn

swyxio commented 3 years ago

oh wow awesome :) :) :) so if this is a working example, what is the question - you want to figure out how to drop the postbuild script, thats the only pain point?

ClaytonFarr commented 3 years ago

It is working, but it feels like it’s doing so in spite of SvelteKit rather than because of it ;)

Ideally, one could create these special functions (that I’ve done in the ‘additional-functions’ directory) as standard SvelteKit endpoints where -

  1. SK included the clientContext data in the function’s ‘context’ object (as Ben noted above), and
  2. The Netlify Adapter provided a mechanism to either choose to build all endpoints as discrete serverless functions with their names intact (instead of within a single ‘render’ function), or to selectively omit specific endpoints and build those separately. (Also curious to see how separate functions v. a single function would affect performance and lambda usage/billing.)

This would allow you to still build everything ‘in-house’ within SK and have the adapter be able to account for the functionality opportunities and expectations of a specific platform, like Netlify.

swyxio commented 3 years ago

i'm no expert but i think 2) is not possible with SvelteKit's current design - it is too predicated on a single "fat function". 1) can be done with some work i think but will need more exposed APIs upstream

ClaytonFarr commented 3 years ago

I haven't dug into other adapters yet but had assumed it was the Netlify Adapter that's making the call to bundled all SK endpoints into a single serverless function called 'render' – and that other adapters, like for node, would likely keep the SK endpoints as discrete elements. Do you mean that's not the case?

swyxio commented 3 years ago

i havent dug into the others either but yeah thats how i think it works. @benmccann can ratify/correct me. i know this isn't exactly how nextjs does it.

reedshea commented 3 years ago

As I work on some prototypes to test out and evaluate SvelteKit + Netlify + Fauna + Stripe + etc., it's so helpful to have this type of discussion available to read and try to learn from. Thank you, @sw-yx and @ClaytonFarr for "working with the garage door up".

A good deal of info seems to be passed along from the Netlify Function, such as most / all of the event headers. That's helpful for things like protecting endpoints - I'm looking at an allow-list of IPs for data ingestion, and it appears that event.headers?.['x-nf-client-connection-ip'] seems to work pretty well for that need (for Netlify specifically). Having the context seems pretty necessary for being able to take advantage of Netlify Identity.

ClaytonFarr commented 3 years ago

Thanks @reedshea, completely agree. I’ve since added more detail and notes to that example repo. I hope that helps -

https://github.com/ClaytonFarr/sveltekit-netlify-stripe-fauna-example

swyxio commented 3 years ago

yeah. we'll need in principle approval for this https://github.com/sveltejs/kit/issues/1779 before i tackle it.

the other thing we can try instead is to NOT handle this inside the netlify adapter, and to modify netlify dev instead to proxy sveltekit endpoints properly. @ascorbic and @jlengstorf seem to be the people to ask here. related issue https://github.com/sveltejs/kit/issues/1286

ascorbic commented 3 years ago

I think a better approach to this is to allow users to create their own Netlify functions as normal, and don't wipe them out with the SvelteKit ones. The easiest way to do that is to not set the functions dir to .sveltekit/netlify/functions, but rather write the entrypoint function to .netlify/functions-internal. That is .gitignored and won't interfere with any user functions.

Regarding access to Netlify context from in SvelteKit, an approach similar to the one suggested by @splatte could work. For Next.js and Gatsby we attach an extra netlifyFunctionParams object to the request that contains the full event and context objects. We could do similar if that helps here.

swyxio commented 3 years ago

after some more thinking I think @ascorbic is correct. I have withdrawn #1779 and think it is better to go with the netlify dev approach