Alex Merced I am a developer, educator, and founder of devNursery.com.

Getting started with SolidStart: A SolidJS framework

5 min read 1664

Getting Started Solid Start

The race for innovation between frontend frameworks has been evolving for quite some time now. While React has innovated with features like JSX, which makes expressing UI logic more declarative, Svelte has introduced compilation to reduce the size of client bundles. On the other hand, SolidJS combines these ideas, along with the smart use of composable primitives and observables.

Parallel to the innovation of these frameworks is the evolution of the meta-frameworks built on top of them, like Next.js for React, SvelteKit for Svelte, and more recently, SolidStart for SolidJS. In this article, we’ll explore SolidStart, consider its features and use cases, and finally, build a simple app. To follow along, you can access the full code for the tutorial on GitHub. Let’s get started!

Table of contents

SolidStart features

SolidJS offers many of the features you’d expect from a meta-framework, including file-based routing, API endpoints, and support for server-side, client-side, and static rendering.

SolidStart also comes equipped with some amazing, unique features, like the ability to use forms to trigger server actions, similar to RemixJS, as well as the ability to easily define RPC functions using its $server function.

It’s important to note that at the time of writing, SolidStart is still in experimental status, and many features may be missing or incomplete. It’s advised to use SolidStart at your own risk. With that said, we can still use SolidStart to set up a simple app.

Setting up our SolidStart app

In this tutorial, we’ll build a simple application that stores data about business trips, including mileage and location. To begin, you should already have Node.js installed on your machine.

First, open up your code editor to an empty folder. If you don’t already have pnpm installed, run npm install -g pnpm. Then, run pnpm create solid.

When prompted to add server-side rendering, select yes. Select no for TypeScript, then choose a bare template. Once the app is generated, run pnpm install.

Folder structure

When using SolidStart, the code you’ll work with will live in the /src folder, which in itself contains some files and folders that you should be familiar with.

  • /components: Store all components that aren’t pages in the /components folder
  • /routes: Store components that are pages in /routes. A page would be the default export of that file
  • root/entry-client/entry-server: Store files that handle the application startup, which we don’t need to touch

Create a /lib folder, which you’ll use to create supporting files, for example, a lot of our API implementation details, support functions, and more.

Defining our business trips data

Instead of using a database, we’ll use an array to demonstrate more simply how to work with our data models. If you’d rather use MongoDB, you can check out this article.

Create a file called src/lib/trips.js:

// The Trips Array
const trips = [
  {
    location: "Miami, FL",
    mileage: 80,
  },
  {
    location: "Savannah, GA",
    mileage: 120,
  },
];


// functions for working with trips we can then convert into API routes or RPC calls
export function getTrips(){
    return trips
}

export function createTrip(newTrip){
    trips.push(newTrip)
    return trips
}

export function updateTrip(id, updatedTrip){
    trips[id] = updatedTrip
    return trips
}

export function deleteTrip(id){
    trips.splice(id, 1)
    return trips
}

In this file, we’ve created a trip array to hold our data, as well as functions for working with our array:

  • getTrips: Returns the array of trips, similar to SELECT * FROM table in SQL
  • createTrip: Takes in an object and creates a new trip by pushing it into an array, simulating a INSERT INTO table VALUES (…) query in a database
  • updateTrip: Updates a trip in the array, similar to UPDATE table WHERE conditions SET updates query
  • deleteTrip: Deletes a trip in the array, similar to DELETE FROM table WHERE conditions query

These functions essentially will simulate having a data model from a database ORM.

Now, we can make these functions usable to our application in two ways. For one, we can write API routes that use these functions. Alternately, in relevant components, we can define RPC calls using these functions with the $server function. Keep in mind that RPC, a Remote Procedure Call, refers to when a function that is run on the server is called from a function call on the client.

Using API routes with SolidStart

Any route can be an API endpoint. The route file just needs to export an async GET/POST/PUT/DELETE function to handle that type of request for that route. If the route renders a page by export defaulting a component, then you can’t define an additional GET for that route.

To show an example of this, we’ll create a file called src/routes/api/trips/(trips).js. Notice that the file name has parenthesis around it. This is a feature of SolidStart that allows you to denote the main file in a folder for a route. So, this file would handle the /api/trips URL. Normally, we would have to name the file index.jsx. After a while, having so many files with the same name gets confusing.



Place the following code in the index.jsx file:

// json function for sending json responses
import { json } from "solid-start";
import { getTrips, createTrip } from "~/lib/trips";
export async function GET(){
    // return the array of trips
    return json(getTrips())
}
export async function POST({request}){
    // get the request body
    const body = await new Response(request.body).json()
    // create new trip
    createTrip(body)
    // return all trips
    return json(getTrips())
}

Now, start your server with npm run dev, and you should be able to use something like Postman or Insomnia to test the routes.

Make a GET request to http://localhost:3000/api/trips/, and you should get all your trips back:

Get Request Solidstart App Trips Returned

Make a POST request to http://localhost:3000/api/trips/ with a JSON body like the one below, and you should see the trip added:

{
    "location": "Manchester,ct",
    "mileage": 1000
}

Trip Added Solidstart Example App

Awesome, we now have working API endpoints, how easy was that!

Bringing in the trips data to the route

In SolidStart, we can pre-fetch data for the route that is available to the page route and all the child components.

We export routeData, and its return value becomes available to the page and subcomponents via the useRouteData Hook.

Let’s set up our main page src/routes/index.jsx to create route data as the result of an API call to our API Route:

import { createRouteData } from "solid-start";


// define our route data, server provided data to frontend
export function routeData() {
return createRouteData(async () => {
  // fetch data from api endpoint
  const response = await fetch("http://localhost:3000/api/trips")
  const data = await response.json()
  return data
});
}

export default function Home() {
  return (
    <main>

    </main>
  );
}

Notice that we used the createRouteData function. This function works a lot like React Query, where we can wrap an asynchronous action with the following benefits:

  • Whenever a route action occurs, the async function will run again, updating our data
  • The data can be given a unique name to assist with caching if we’re using the same data in multiple routes

We’ll see in our trip component how we can use actions and route data.

The Trips component

Now, we’ll create a file to put this all together in src/components/Trips.jsx:

import { createRouteAction, useRouteData} from "solid-start";
import { createTrip } from "~/lib/trips";
import server$ from "solid-start/server";
export default function Trips() {
  // bring the route data into our component
  const trips = useRouteData();
  // Define an RPC call of what we want to run on the server
  const makeTrip = server$(async (trip) => createTrip(trip))
  // define a form for creating a trip using solid-states action system
  const [_, { Form }] = createRouteAction(async (formData) => {
    // create the new trip object
    const trip = {
        location: formData.get("location"),
        mileage: formData.get("mileage")
    }
    // pass object RPC call to create new trip on server
    makeTrip(trip)
  });
  return (
    <div>
      <ul>
        {trips()?.map((trip) => (
          <li>{trip.location} - mileage: {trip.mileage}</li>
        ))}
      </ul>
      <Form>
          <input type="input" name="location" placeholder="location"/>
          <input type="number" name="mileage" placeholder="mileage"/>
          <input type="submit"/>
      </Form>
    </div>
  );
}

In this component, we use many SolidStart features; for one, we use useRouteData to get the routeData defined in the page component. server$ defines a function that runs solely on the server. In this case, we want the function that creates Trips to run only on the server since it wouldn’t work on the client.

Finally, createRouteAction creates a function and the corresponding Form component. The Form component calls the function that acts as an action, triggering a refetch of routeData.


More great articles from LogRocket:


Displaying the Trips component

Edit your src/routes/index.jsx as follows:

import { createRouteData } from "solid-start";
import Trips from "~/components/Trips";

// define our route data, server provided data to frontend
export function routeData() {
return createRouteData(async () => {
  // fetch data from api endpoint
  const response = await fetch("http://localhost:3000/api/trips")
  const data = await response.json()
  return data
});
}

export default function Home() {
  return (
    <main>
      <Trips/>
    </main>
  );
}

Using the useRouteData Hook, we export the routeData() function, which allows us to pre-fetch data to be used by the page and it’s subcomponents.

The createRouteData creates a resource that will refetch anytime an action like the one our form triggers on submit occurs. Finally, the <Trips/> component displays our trips and form.

Conclusion

Hopefully, you’ve gotten a sense of just how powerful the SolidStart framework can be. Although it is still experimental at the time of writing, SolidStart has a promising future. If you want to see other variations of what you can do with SolidStart, check out the following builds:

 

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Alex Merced I am a developer, educator, and founder of devNursery.com.

Leave a Reply