Skip to content

Creating Data on the Server

Estimated Time: 5min Difficulty: Easy Version: 0.1.*

Context

Will teach you how to create an Item for the previous defined DataType

Prerequisites

  • raclette CLI installed
  • raclette Server running
  • Node.js 24+
  • You have finished the setting up a todo plugin example and it's up and running

Declare create Service

In our ./plugins/example-todoplugin/backend/todo.service.ts we now want to define the service logic for our data creation

typescript
import type {
  Todo,
  Todo as TodoType,
} from "./todo.schema"
import type { QueryOptions } from "@_/types/service"
import { v4 as uuidv4, validate } from "uuid"
import { createTodoPayload } from "./helpers/todoHelper"
import type {
  PluginFastifyInstance,
  FrontendPayload,
  FrontendPayloadRequestData,
} from "@raclettejs/core/types"
import { Model } from "mongoose"

export class TodoService {
  private todoModel: Model<Todo>

  constructor(model: Model<Todo>) {
    this.todoModel = model
  }
  [...]
   async _createTodo(
     fastify: PluginFastifyInstance,
     todoBody: TodoCreate,
   ): Promise<TodoType> {
     try {
       if (todoBody._id) {
         const uuidValid = validate(todoBody._id)
   
         if (!uuidValid) {
           throw new Error("Invalid ID - not a valid uuid v4")
         }
   
         const duplicate = await this.todoModel.findById(todoBody._id)
   
         if (duplicate) {
           throw new Error("An entry with this id already exists")
         }
       } else {
         todoBody._id = uuidv4()
       }
   
       const todo = new this.todoModel(todoBody)
   
       await todo.save()
       fastify.log.info(`[API] Created todo #todo._id}`)
   
       return todo.toObject ? todo.toObject() : todo
     } catch (err: any) {
       fastify.log.error(err.message)
       throw err
     }
   }
   
   /**
   * Create a new todo with payload wrapping and event emission
   */
   async createTodo(
     fastify: PluginFastifyInstance,
     requestData: FrontendPayloadRequestData,
     todoBody: TodoCreate,
   ): Promise<FrontendPayload<TodoType[]>> {
     const todo = await this._createTodo(fastify, todoBody)
   
     const payload = await createTodoPayload(fastify, [todo], requestData)
     if (requestData.broadcast) {
       fastify.emit("todoCreated", payload)
     }
   
     return payload
   }
  [...]
}

export const createTodoService = (model: Model<Todo>) => {
  return new TodoService(model)
}

Declare route on the backend side

In our ./plugins/example-todoplugin/backend/routes/ we want to create a new route called todo.create.ts

typescript
import type { Static } from "@sinclair/typebox"
import type { FastifyReply, FastifyRequest } from "fastify"
import { Type } from "@sinclair/typebox"
import type { PluginFastifyInstance } from "@raclettejs/types"

const ParamsSchema = Type.Object({
  _id: Type.String(),
})
type Params = Static<typeof ParamsSchema>

export default (fastify: PluginFastifyInstance) => {
  const handler = async (
    req: FastifyRequest<{
      Params: Params
    }>,
    reply: FastifyReply,
  ) => {
    try {
      // Add owner and lastEditor from the authenticated user
      const todoData = {
        ...req.body,
        owner: req.user._id,
        lastEditor: req.user._id,
      }
      
      const payload = await fastify.custom.todoService.createTodo(
        fastify,
        req.requestParams,
        todoData,
      )
      
      return reply.status(201).send(payload)
    } catch (err: any) {
      fastify.log.error(err.message)
      return reply.internalServerError(err.message)
    }
  }

  return {
    handler,
    onRequest: [fastify.authenticate],
    config: {
      type: dataCreate,
      broadcastChannels: ["todoCreated", "dataCreated"],
    },
    schema: {
      summary: "Example todo post Route",
      description: "Example todo create Route",
      tags: ["myApp/todo"],
      body: todoCreateSchema,
    },
  }
}

Creating data in our widget

Now we can query our endpoint with the frontendApi fron our component

typescript
const { $data, [...] } = usePluginApi()
const {data, dataArr, response, query, execute, isLoading, error } = $data.todo.create({
  params: {},
  id: false,
  options: {
    immediate: false,
    cb: (result) => {},
    notify: true | false,
    responseType: "json",
    mode: "none" | "cors",
    useCache: true | false,
    useStore: true | false,
  },
  config: {}
})
Returns
  • data - The resultobject
  • dataArr - The resultArray
  • response - The response from the server
  • query - the query object (only in reading queries)
  • execute - an awaitable funciton to trigger the action call. The response and result returned from this functions are not reactive! Use dataArr or data
  • isLoading - a boolean indicator for the loading/fetch state
  • error - contains the error object if errors are catched
Props
  • id - if set this will be used instead of the query instance nanoId
  • params - The object you want to send to the backend
  • options
    • immediate - If true, the action will be executed right away, defaults to false
    • cb - A function which will get called after the action is resolved. Receives the result object
    • useStore - If false, will not write the data to the redux store. The data return will also not be available, use response instead. defaults to true
    • notify - if false, will not write into the notification que and snackbar, defaults to true
    • responseType - only used if you want to explicitly receive a http stream. Set to stream in that case, defaults to json
    • mode - if responseType is stream, you can set mode "cors" here

After defining our action and the corresponding outputs we can now trigger the action when needed.

typescript
const createData = async (newItem) => {
  // log the action outputs before and after to see what happens!
  console.log(data, query, execute, isLoading, error)
  const { result, response } = await execute(newItem)
  // log the action outputs before and after to see what happens!
  console.log(result, response)
  console.log(data, query, execute, isLoading, error)
}

TIP

Be aware that the returns of execute are not reactive like the data and dataArr