Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add app and server to server setupFn #1017

Merged
merged 12 commits into from
Feb 27, 2023
Merged

Conversation

shayneczyzewski
Copy link
Sponsor Contributor

@shayneczyzewski shayneczyzewski commented Feb 20, 2023

Description

This PR adds app and server to the server setupFn, mainly as an escape hatch. Users can then do arbitrary Express endpoints, or things like websockets.

It is the start of #268, but the real finish there is adding some proper api DSL support.

Select what type of change this PR introduces:

  1. Just code/docs improvement (no functional change).
  2. Bug fix (non-breaking change which fixes an issue).
  3. New feature (non-breaking change which adds functionality).
  4. Breaking change (fix or feature that would cause existing functionality to not work as expected).

Update Waspc ChangeLog and version if needed

If you did a bug fix, new feature, or breaking change, that affects waspc, make sure you satisfy the following:

  1. I updated ChangeLog.md with description of the change this PR introduces.
  2. I bumped waspc version in waspc.cabal to reflect changes I introduced, with regards to the version of the latest wasp release, if the bump was needed.

@@ -5,7 +5,8 @@ let someResource = undefined

export const getSomeResource = () => someResource

const setup = async () => {
const setup = async (app, _server) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend adding a type for the ServerSetupFn like:

type ServerSetupFn = (context: ServerSetupFnContext) => void;

type ServerSetupFnContext = {
  app: express.Application,
  server: http.Server,
};

which the user can import like import { ServerSetupFnContext } from '@wasp/types'; or similar.

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, can do. 👍🏻 This is just a WIP skeleton at the moment but will beef it out tomorrow some more before tagging for reviews.

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some types, thanks!

Comment on lines 4 to 6
export type Application = express.Application

export type Server = http.Server
Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on exporting type aliases like these? I would like people to be able to pass around/use the components of ServerSetupFnContext and still have proper typing, but without having to import the actual express/node imports themselves. I think it makes it nice they can get it all from the same import path, but curious on the TS expert pros/cons here. The downside is they will still need to know it is actually an express.Application if they want to use it with other libraries, look up docs, etc. but I hope that is an ok use for aliases? Thanks!

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, see function addCustomRoute(app: Application) in serverSetup.ts.

Copy link
Contributor

@infomiho infomiho Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the reexport in our context, so users can import from @wasp/types 👍

I would maybe write it a bit differently:

import { Application } from 'express';
import { Server } from 'http';

export type ServerSetupFn = (context: ServerSetupFnContext) => Promise<void>

export type ServerSetupFnContext = {
 app: Application,
 server: Server,
}

export { Application } from 'express';
export { Server } from 'http';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I will leave this to @sodic and @infomiho .
I need to take some time these days to learn TS but it is not yet that day :D!

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thanks @infomiho for a better convention to use!

{=# doesServerSetupFnExist =}
await {= serverSetupJsFnIdentifier =}()
const serverSetupFnContext: ServerSetupFnContext = { app, server }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shayneczyzewski Why are we exposing the server object? I'm not sure for which use case it's needed

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many websocket libraries wrap the server. For example: https://socket.io/get-started/chat#integrating-socketio That was one main use case I was hoping to cover here as well (and that won't be covered by the api DSL next step).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I though that socket.io didn't need the server 🤦‍♂️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was doing this for an experiment, I did it as described here: https://discord.com/channels/686873244791210014/920312576133443634/1070052697572638870 -> so I passed app to setupFn, but I didn't move the server creation above it and then pass it.

I think it is cool that you did, that gives people more stuff to work with!

But it does change behavior a bit -> before, setupFn was allowing you to do stuff before server is created. Now, it allows you to set up stuff after server is created. I have no idea if we loose anything with that? Is there a use case where it might be important to do something before server is created from app? If we are not sure now, that is ok, we can just go with this and if we learn there is use case for it, we can add another function called preSetupFn or smth, that will be called just before the server.

One thing I thought was important was to add routes to app before the server(app) is called, but I guess I was wrong because I am sure you tested this and it works, even though you add stuff to app after the server(app) is called. Interesting. But does it work because it should, or by accident?

Maybe we can get some answers here: https://nodejs.org/api/http.html#class-httpserver .

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question @Martinsos - I don't think there is any technical reason why we have to keep it after, but you are right it does change the previous implied semantics a bit from user-level docs (which I massaged to say "before the server starts accepting requests"). But I don't think there is any actual technical impact in doing this swap. The custom route I setup in the setupFn still worked even when createServer happened before. I am not aware of any other potential impacts of this.

I believe createServer is just registering a request handler callback (of which an Express instance itself is). That callback should be using things internally that can be set either before or after that registration (i.e. it shouldn't even know it was registered to me). But I will do more testing and digging to be 100% sure (including doing a Socket.IO test too).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good @shayneczyzewski !

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not pretty (we have to specify the client/server URLs since different domains) but you can get Socket.IO up and running with access to server: https://gist.github.com/shayneczyzewski/165b4266d77a2a75454f605dd55b5db7

Copy link
Member

@Martinsos Martinsos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shayneczyzewski this is it from me, I will let @sodic or @infomiho handle the TS side of this!

{=# doesServerSetupFnExist =}
await {= serverSetupJsFnIdentifier =}()
const serverSetupFnContext: ServerSetupFnContext = { app, server }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was doing this for an experiment, I did it as described here: https://discord.com/channels/686873244791210014/920312576133443634/1070052697572638870 -> so I passed app to setupFn, but I didn't move the server creation above it and then pass it.

I think it is cool that you did, that gives people more stuff to work with!

But it does change behavior a bit -> before, setupFn was allowing you to do stuff before server is created. Now, it allows you to set up stuff after server is created. I have no idea if we loose anything with that? Is there a use case where it might be important to do something before server is created from app? If we are not sure now, that is ok, we can just go with this and if we learn there is use case for it, we can add another function called preSetupFn or smth, that will be called just before the server.

One thing I thought was important was to add routes to app before the server(app) is called, but I guess I was wrong because I am sure you tested this and it works, even though you add stuff to app after the server(app) is called. Interesting. But does it work because it should, or by accident?

Maybe we can get some answers here: https://nodejs.org/api/http.html#class-httpserver .

Comment on lines 4 to 6
export type Application = express.Application

export type Server = http.Server
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I will leave this to @sodic and @infomiho .
I need to take some time these days to learn TS but it is not yet that day :D!

Copy link
Member

@Martinsos Martinsos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from me, will let @infomiho and/or @sodic comment on TS stuff.

shayneczyzewski and others added 2 commits February 23, 2023 09:44
@shayneczyzewski shayneczyzewski changed the title WIP: Add app and server to server setupFn Add app and server to server setupFn Feb 24, 2023
@shayneczyzewski shayneczyzewski marked this pull request as ready for review February 24, 2023 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants