Server Functions

Server functions are specialized microservices that at transpile time are converted into API entry points.

To flag a function as a server function, you must declare it as static async.

Being a static function means it has no access to the instance scope.

However, instead of calling the static version from the class, you must invoke it as an instance function.

Server functions can be called anytime in your code and are not limited to prerender steps.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  static async increment(context) {
    context.count++;
  }

  async handleClick() {
    await this.increment();
  }

  // ...

}

export default Component;

You can also call static methods directly which is useful for some code patterns that invoke external server functions.

import Nullstack from 'nullstack';
import UserStore from './UserStore';

class Component extends Nullstack {

  async initiate() {
    this.users = await UserStore.getAllUsers()
  }

  // ...

}

export default Component;

✨ Learn more about the server context.

Client behavior

When you call a server function from the client, the arguments will be serialized as JSON.

The arguments will be posted against the automatically generated API and merged with the server context when it reaches the server.

The return value of the server function will be serialized back to the client and can be seamlessly used as if it were a local function.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  static async increment(context) {
    context.count++;
    return context.count;
  }

  async handleClick() {
    this.count = await this.increment();
  }

  // ...

}

export default Component;

Server behavior

Server functions will be used as local functions, simply aliasing the instance call to the class and merging the arguments with the server context.

Date Convenience

Dates are serialized as UTC in JSON and deserialized back to Date objects.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  async initiate() {
    const date = new Date();
    const verified = this.verifyDay({date});
  }

  static async verifyDay({date}) {
    return date.getDay() === new Date().getDay();
  }

  // ...

}

export default Component;

Fetch Convenience

fetch is available in both server and client functions for the sake of isomorphy.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  // ...

  async initiate() {
    const url = 'https://api.github.com/repos/nullstack/nullstack/issues';
    const response = await fetch(url);
    this.issues = await response.json();
  }

  // ...

}

export default Component;

Server only imports

Imported dependencies that are only used inside server functions will be excluded from the client bundle.

This is useful for both accessing node.js exclusive modules and reducing the client bundle size by preprocessing data like markdown without having to expose the dependency to the end-user.

import Nullstack from 'nullstack';
import {readFileSync} from 'fs';
import {Remarkable} from 'remarkable';

class Application extends Nullstack {

  static async getTasks() {
    const readme = readFileSync('README.md', 'utf-8');
    return new Remarkable().render(readme);
  }

  // ...

}

export default Application;

Security

Keep in mind that every server function is similar to an Express route in API and must be coded without depending on view logic for security.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  static async getCount({request, count}) {
    if(!request.session.user) return 0;
    return count;
  }

  // ...

}

export default Component;

Server functions with the name starting with "_" do not generate an API endpoint and do not have access to the context by default to avoid malicious API calls.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  static async _getCount({ request }) {
    return request.count;
  }

  static async getDoubleCount({ request }) {
    if(!request.session.user) return 0;
    return this._getCount({ request }) * this._getCount({ request });
  }

  // ...

}

export default Component;

💡 Server functions are not exposed to the client.

✨ Learn more about the jsx elements.

Reserved words

Server function names cannot collide with instance method names from the current class or its parent classes.

The following words cannot be used in server functions:

  • prepare
  • initiate
  • launch
  • hydrate
  • update
  • terminate

Server functions named start will not generate an API endpoint and can only be called by other server functions.

Reserved HTTP method prefixes

Server functions declared with their names starting with HTTP verbs will be executed accordingly to the respective HTTP method. Supported verbs are:

  • get
  • post
  • put
  • patch
  • delete
import Nullstack from "nullstack";

class HTTPVerbs extends Nullstack {
  // this is a GET request
  static async getUserById({ id }) {
    // ...
  }

  // this is a POST request
  static async postUser({ data }) {
    // ...
  }

  // this is a PUT request
  static async putUserById({ id, data }) {
    // ...
  }

  // this is a PATCH request
  static async patchUserById({ id, data }) {
    // ...
  }

  // this is a DELETE request
  static async deleteUserById({ id }) {
    // ...
  }

  // ...
}

export default HTTPVerbs;

💡 Server functions without those special prefixes will be defaulted to a POST request.

🔥 Be mindful to the specification of each HTTP method. For example, GET requests have a limit of 2kb of data that can be passed as a parameter, so attempting to send an entire object to a server function may result in failure.

Performance Considerations

Server functions are just API endpoints in the end of the day. Be mindful of this when making function calls, and try to keep the payload as small as possible.

import Nullstack from 'nullstack';

class Component extends Nullstack {

  // ✅ do this 
  static async getUserProfileById({ id }) {
    // ...
  }

  async hydrate() {
    this.profile = await this.getUserProfileById({id: this.user.id})
  }

  // 🚫 do not do this 
  static async getUserProfile({ user }) {
    const id = user.id
    // ...
  }

  async hydrate() {
    this.profile = await this.getUserProfile({user: this.user})
  }

  // ...

}

export default Component;

Caveats

Automatically generated API endpoints are not meant to be used by 3rd-party apps.

The URL and implementation may change between versions of Nullstack.

Server functions endpoints are based on file path and function name, changing those might cause backwards compatibility problems in some scenarios.

✨ If you want to build an API, learn more about how to create an API with Nullstack.

Next Step

➡️ Learn more about Core Features: ContextHave any questions or suggestions? Join our Discord