Services in Remix

creating light service clients for server->server requests w/ Remix.

/********* 
Folder Structure

app
|--- models (where prisma models go)
|--- services (where I place my server-side encapsulated logic)
    |--- service_folder 
        |--- service.server.ts
        |--- service.types.ts
        |--- service.config.ts
        
***********/


// app/services/notes/notes.types.ts
export type NotesServiceConfig = {
  BASE_URL: string;
  API_ID?: string;
  API_SECRET?: string;
}

expor type Note = {
  id: string;
  userId: string;
  title: string;
  body: string;
  createdAt: string; // will convert when needed
  
}

// app/services/notes/notesService.config.ts
import {NotesServiceConfig} from "./notes.types.ts"

export const NotesConfig: NotesServiceConfig = {
  BASE_URL: "https://link.to.site/base/api/path",
  API_ID: process.env.SOME_API_APP_ID,
  API_SECRET: process.env.SOME_API_SECRET
}


// app/services/notes/notesService.server.ts
import {axios} from "axios" // NOTE: maybe use fetch if there on server?
import {NotesConfig as config} from "./notes.config.ts"
import {Note} from "notes.types.ts"

const setHeaders = (config: NotesServiceConfig) => {
  return {
    headers: {
      "api-id-header": config.API_ID,
      "api-secret-header": config.API_SECRET
    }
  }
}

export const getNotes = async (userId: string): Note[] => {
  const { data } = await axios.get(`${BASE_URL}/notes/${userId}`,
    {...setHeaders(config)}
  )
  
  return data
}

// app/routes/notes/index.tsx

...

// loader function
export const loader: LoaderFunction = async({params}) = {
  // get userId from session
  const userId = await requireUserId(request);
  
  const notes = await getNotes(userId)
  
  // handle error if notes don't exist or throws an error
  
  return notes
}

export default function Component() {
  const notes = useLoaderData<Note[]>();
  ...
}