Package Exports
- @vch-lt/path
- @vch-lt/path/next
- @vch-lt/path/nuxt
- @vch-lt/path/server
Readme
@vch-lt/path
TypeScript SDK for integrating custom course websites with the Path LMS (VCH).
Handles authentication, enrollment data, and progress tracking. Works client-side (any framework) and server-side (Next.js App Router, Nuxt, Express, etc.).
How it works
Authentication uses a bearer token stored in a cookie on your course domain:
- Your course page calls
path.login()→ user is redirected to the Path login page - After login, Path redirects back to your course page with
?path-token=...in the URL - The SDK automatically reads the token, stores it in a cookie, and cleans the URL
- All subsequent API calls include the token as an
Authorization: Bearerheader - Your server-side code reads the same cookie to make authenticated requests
Installation
npm install @vch-lt/pathClient-side usage
Works in any browser environment — vanilla JS, Vue, React, Svelte, etc.
import pathSDK from "@vch-lt/path";
const path = pathSDK({
courseId: "your-course-id",
courseUrl: "https://your-course-site.com",
// pathUrl: 'https://path.vchlearn.ca', // optional, this is the default
});Initialize on page load
Create the SDK instance as early as possible (e.g., in your app entry point). On first instantiation it automatically reads ?path-token= from the URL, stores it in a cookie, and removes it from the URL bar.
import pathSDK from "@vch-lt/path";
const path = pathSDK({ courseId: "...", courseUrl: "..." });Check authentication
const auth = await path.checkAuth();
if (!auth.isAuthenticated) {
// Pass the current URL so the user returns to the right page after login
path.login(window.location.href);
return;
}
console.log(auth.enrollment?.user.email);
console.log(auth.enrollment?.progress);Submit progress
await path.submitProgress("module-1-complete", true);
await path.submitProgress("quiz-score", 85);
await path.submitProgress("chapter", "introduction");Get enrollment and progress
const enrollment = await path.getEnrollmentAndProgress();
console.log(enrollment.progress);Logout
await path.logout();Next.js App Router (Server Components)
Use @vch-lt/path/next in Server Components, Route Handlers, and Server Actions. It automatically reads the bearer token from the Next.js cookie store — no token passing required.
Setup
Set your course ID in the environment so you never have to pass it explicitly. Use NEXT_PUBLIC_PATH_COURSE_ID so it's available on both the server and in Client Components:
# .env.local
NEXT_PUBLIC_PATH_COURSE_ID=your-course-id
NEXT_PUBLIC_PATH_COURSE_URL=https://your-course-site.comMiddleware (required for SSR auth on first login)
Without middleware, the bearer token arrives as ?path-token= in the URL on redirect from Path login. The cookie isn't set until client-side JS runs, so the first SSR render would see an unauthenticated request.
The pre-built middleware fixes this by reading ?path-token=, setting the cookie on a redirect response, and cleaning the token from the URL bar. The browser follows the redirect to the clean URL with the cookie already set, so the SSR render sees an authenticated request.
// middleware.ts
export { pathMiddleware as default, pathMiddlewareConfig as config } from "@vch-lt/path/next";If you already have middleware, compose them:
// middleware.ts
import { createPathMiddleware } from "@vch-lt/path/next";
import type { NextRequest } from "next/server";
const handlePath = createPathMiddleware();
export function middleware(request: NextRequest) {
const pathResponse = handlePath(request);
if (pathResponse) return pathResponse;
// ... your own middleware logic
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};Server Component
Required: Pages that call
pathServerSDKmust useexport const dynamic = 'force-dynamic'. Next.js intentionally disablescookies()in statically rendered routes — the cookie store will always appear empty andcheckAuth()will always returnisAuthenticated: falsewithout this.
// app/course/page.tsx
import { pathServerSDK } from "@vch-lt/path/next";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default async function CoursePage() {
const path = await pathServerSDK(); // courseId read from PATH_COURSE_ID env var
const auth = await path.checkAuth();
if (!auth.isAuthenticated) {
redirect("/login");
}
const enrollment = auth.enrollment!;
return (
<div>
<h1>Welcome, {enrollment.user.email}</h1>
<p>Progress: {enrollment.progress?.length ?? 0} milestones completed</p>
</div>
);
}You can also pass courseId explicitly if you prefer:
const path = await pathServerSDK({ courseId: "your-course-id" });Route Handler
// app/api/progress/route.ts
import { pathServerSDK } from "@vch-lt/path/next";
import { NextResponse } from "next/server";
export async function GET() {
const path = await pathServerSDK();
const enrollment = await path.getEnrollmentAndProgress();
return NextResponse.json(enrollment);
}Server Action
// app/actions.ts
"use server";
import { pathServerSDK } from "@vch-lt/path/next";
export async function completeModule(milestoneSlug: string) {
const path = await pathServerSDK();
return path.submitProgress(milestoneSlug, true);
}Note:
pathServerSDKis server-only. For client-side interactions (login redirect, logout), usepathClientSDKfrom@vch-lt/path/next(orpathSDKfrom@vch-lt/path) in a Client Component:// app/components/LoginButton.tsx "use client"; import { pathClientSDK } from "@vch-lt/path/next"; const path = pathClientSDK({ pathUrl: "https://path.vchlearn.ca" }); export function LoginButton() { return <button onClick={() => path.login(location.href)}>Login</button>; }
Nuxt Module (recommended for Nuxt apps)
Use @vch-lt/path/nuxt for zero-config Nuxt integration. The module:
- Auto-registers the path-token middleware (SSR auth on first login, no extra redirect)
- Auto-imports
usePath()— a universal composable that works on server and client transparently - Auto-imports
pathNuxtSDKfor lower-level use in server routes - Adds a client plugin that initializes the token from the URL on login redirect
Setup
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@vch-lt/path/nuxt"],
path: {
courseId: "your-course-id",
// pathUrl: 'https://path.vchlearn.ca', // optional, this is the default
},
});Pages and components
usePath() is auto-imported. Data-fetching methods use useAsyncData internally so they fetch on the server during SSR and reuse that result on the client — no double-fetch, no extra server route needed:
<!-- pages/course.vue -->
<script setup lang="ts">
const path = usePath();
// checkAuth() and getEnrollmentAndProgress() work on server and client
const { data: auth } = await path.checkAuth();
if (!auth.value?.isAuthenticated) {
path.login(window.location.href);
}
</script><!-- components/ProgressButton.vue -->
<script setup lang="ts">
const path = usePath();
const complete = async () => {
await path.submitProgress("module-1", true);
};
</script>usePath() methods
| Method | Returns | Description |
|---|---|---|
checkAuth() |
AsyncData<AuthStatus> |
Auth status + enrollment. SSR-safe. |
getEnrollmentAndProgress() |
AsyncData<Enrollment> |
Full enrollment data. SSR-safe. |
submitProgress(slug, value) |
Promise |
Record a milestone. Works on server and client. |
login(callbackUrl?) |
void |
Redirect to Path login. Client-only. |
logout() |
Promise |
Clear session and log out. Client-only. |
Server routes (advanced)
pathNuxtSDK is also auto-imported for cases where you need a server route:
// server/api/something.get.ts
export default defineEventHandler(async (event) => {
const path = await pathNuxtSDK(event);
return path.getEnrollmentAndProgress();
});Low-level Nuxt/H3 usage (without the module)
If you need more control, use @vch-lt/path/nuxt directly:
import { pathNuxtSDK, createPathH3Middleware } from "@vch-lt/path/nuxt";Generic server-side usage
Use @vch-lt/path/server for Express or any other server framework. You read the token from your framework's cookie store and pass it in.
import { pathServerSDK, TOKEN_STORAGE_KEY } from "@vch-lt/path/server";Express
import { pathServerSDK, TOKEN_STORAGE_KEY } from "@vch-lt/path/server";
import cookieParser from "cookie-parser";
app.use(cookieParser());
app.get("/progress", async (req, res) => {
const token = req.cookies[TOKEN_STORAGE_KEY] ?? null;
const path = pathServerSDK({ courseId: "your-course-id", token });
res.json(await path.getEnrollmentAndProgress());
});API Reference
pathSDK(config) — client-side
| Option | Type | Required | Description |
|---|---|---|---|
courseId |
string |
No | Your course ID from Path (falls back to VITE_PATH_COURSE_ID / NEXT_PUBLIC_PATH_COURSE_ID / PATH_COURSE_ID env var) |
courseUrl |
string |
Yes | Your course website URL |
pathUrl |
string |
No | Path backend URL (default: https://path.vchlearn.ca) |
Returns an SDK instance with:
| Method | Description |
|---|---|
checkAuth() |
Returns { isAuthenticated, enrollment? } |
login(callbackUrl?) |
Redirects to Path login. Pass window.location.href to return to current page |
logout() |
Clears token and calls logout API |
getEnrollmentAndProgress() |
Returns full enrollment + progress data |
submitProgress(milestoneSlug, value) |
Records progress for a milestone |
pathServerSDK(config?) — Next.js server-side
Import from @vch-lt/path/next. All config fields are optional and fall back to environment variables:
| Option | Env var fallbacks | Description |
|---|---|---|
courseId |
NEXT_PUBLIC_PATH_COURSE_ID, PATH_COURSE_ID |
Your course ID from Path |
courseUrl |
NEXT_PUBLIC_PATH_COURSE_URL, COURSE_URL |
Your course URL (used for login redirect) |
pathUrl |
PATH_URL |
Path backend URL (default: https://path.vchlearn.ca) |
Returns a Promise of a server SDK instance with checkAuth(), getEnrollmentAndProgress(), submitProgress(), getLoginUrl(), and login().
pathSDK(config) — Next.js client-side
Import from @vch-lt/path. Use in Client Components for login, logout, and client-side data fetching.
pathNuxtSDK(event, config) — Nuxt/H3 server-side
Import from @vch-lt/path/nuxt. Takes an H3 event and optional config (courseId, pathUrl). Returns a Promise of a server SDK instance. When using the Nuxt module, pathNuxtSDK(event) is auto-imported with courseId pre-configured.
usePath() — Nuxt universal composable (Nuxt module only)
Auto-imported. Works in pages, components, and layouts on both server and client. Data-fetching methods (checkAuth, getEnrollmentAndProgress) return AsyncData (Nuxt's useAsyncData result) — SSR-safe with no double-fetch. Action methods (login, logout, submitProgress) return Promises.
pathServerSDK(options) — generic server-side
Import from @vch-lt/path/server. Takes courseId, pathUrl, and token: string | null. Synchronous — returns the SDK instance directly.
Cookie details
The bearer token is stored in a cookie named path_bearer_token on your course domain with SameSite=Lax; Secure. This means:
- It is readable by your course server (for SSR)
- It is not sent cross-origin (Path's server never receives it as a cookie — only as a
Bearerheader) - It survives page reloads and navigation within your course site
- It is cleared when the user logs out