waspc/docs/design-docs/server-setup.md
Right now, only code that dev can write that executes on server are operations (actions and queries) and any code that they import. They are executed when called by client.
In case that dev wants to write a piece of code that executes on server start, in order to perform some kind of setup, e.g. connect to the database or schedule a cron job, they can't do it.
Additionally, dev currently can't in any way influence or modify default server setup that Wasp performs, which might prove to be too rigid in the future. While this is a separate concern, it is closely related to the execution of code on server start and is worth considering it at the same time.
NOTE: What dev can currently do, is implement a JS singleton which can then be imported in JS operations and used there. Such singleton could be used to allocate resources the very first time it is called, e.g. it could establish connection to the database in a lazy manner. This solves the problem above somewhat, in case when it is ok that resource allocation happens lazily on first request. It can't be used to customize Wasp setup though, and you can't perform the setup before the very first request, upfront, which means it can't be used to e.g. define a scheduled job.
I found that people are requesting same feature from NextJS, in following discussions:
They want to run some custom setup, like:
NextJS doesn't have a solution for this, instead their official answer is that you should create a standalone microservice for that, or that you can use a custom server feature where you use next programmatically in your custom nodejs server, but then you lose a lot of benefits that NextJS provides.
The solution that people suggested was an async function that returns an object (with e.g. allocated resources) that will be included in the "context" that is then passed around to the operations.
context).context, function could return a function that modifies the context.
This gives more control to dev, but it can also lead to them messing up context.context, but also other things that modify how Wasp works.
For example, it could return an expressJS router that will be added to the expressJS router created by Wasp.
This way, dev can extend different parts of Wasp while not being able to mess up things, since they don't modify existing configuration directly.
Instead, they return pluggable parts and Wasp plugs them in in the right places.New server declaration (in .wasp) with setup field:
server: {
setup: {
fn: import { myCustomSetup } from '@ext/serverSetup.js'
}
}
Function could be defined as:
// In '@ext/serverSetup.js'
export const myCustomSetup = async () => {
const someResource = await setupSomeResource();
return { someResource };
};
Resources returned during setup could be used from operations as:
export const myAction = async (args, context) => {
console.log(context.server.setup.someResource);
};
I considered adding server as not a standalone declaration but a field of app declaration, but felt that would be too crowded and we already have stuff like dependencies and auth, so we already decided to go down the route where we don't put everything under the app and this way we are consistent with that.
I also considered multiple ideas on how to put the returned object in the context, and found that server.setup sounds specific enough that it will not clash with anything else nor will need changing in the future.
For the very first version, I will go with basic requirements -> no args, and returned object goes under context.server.setup.
In the future we can consider implementing some of the advanced requirements.
NOTE: I actually ended up simplifying the MVP even further.
Instead of server { setup { fn: ... } }, I went with server { setupFn: ... } since it was less work to implement it.
Also, I didn't implement injection of returned object into context, since it is not simple and is not really needed.
If dev wants to make resources available to the rest of the code, they can instead make the module that exposes setup function a "singleton" and set the values there.
Then other parts of the code can import that module and directly access those values. No need for injection via context.