Fixing Academy Course Search
Now that we’re able to login, naturally the next thing we’d want to do is be able to search for courses that we can enroll in, right?
Well, clearly not - because its broken!

We’re seeing the error pop up in Sentry, but how can we use Tracing and Logs to help troubleshoot the issue quicker?

Learning Objectives
By the end of this module, you will:
- Create custom spans to track the course search flow
- Use Trace Explorer to search for specific spans that match issues in your environment
- Compare values across spans to understand where its breaking in the application
- Extend our logs to include context around the search issues in the application
Frontend Implementation
Let’s start by instrumenting the frontend search functionality to track search requests and parameters being sent to the backend.
-
Add Sentry Import to Frontend API Service
Navigate to the
/apps/frontend/src/services/api.ts
file and add the Sentry import at the top:import * as Sentry from '@sentry/react'; -
Set Up Frontend Search Logging
Add the logger destructuring after your import to access Sentry’s logging functionality:
const { logger } = Sentry -
Replace the Complete Search Courses Method
Replace the
search.courses
method with this instrumented version:search: {courses: (query: string) =>Sentry.startSpan({name: 'search.courses.frontend',op: 'http.client',attributes: {'search.query': query,'http.url': `/search/courses?query=${encodeURIComponent(query)}`,},},() => {logger.info(logger.fmt`Searching courses with query: ${query}`);return fetchApi<any[]>(`/search/courses?query=${encodeURIComponent(query)}`);}),}, -
Test Search Tracing
Try searching for courses again. Navigate to Explore > Traces in Sentry’s left navigation menu, and search using the
span.description
filter to find your search-related spans using thesearch.courses.frontend
value. -
Examine the Trace
Clicking into one of the spans will show you the trace waterfall where you can examine the properties of any of the spans submitted.
-
Examine the Logs
Check the Explore > Logs tab for the log entries created by
logger.info
andlogger.error
calls.
Backend Implementation
Now let’s instrument the backend search functionality to trace server-side processing and identify the parameter mismatch issue.
-
Add Sentry Import to Backend Search Routes
Navigate to the
/apps/server/src/modules/search/routes.ts
file and add the Sentry import at the top:import * as Sentry from '@sentry/node'; -
Set Up Backend Search Logging
Add the logger destructuring after your import:
const { logger } = Sentry -
Replace the Complete Search Route
Replace the entire
searchRoutes.get('/search/courses'
route with this instrumented version:searchRoutes.get('/search/courses', async (req, res) => {try {const { q } = req.query;await Sentry.startSpan({name: 'search.courses.server',op: 'db.search',attributes: {'search.query': typeof q === 'string' ? q : String(q || ''),},},async (span) => {logger.info(logger.fmt`Backend received query parameters: ${q}`);// Add query validation attributesspan.setAttributes({'search.query_provided': !!q,});// Realistic API validation - backend expects 'q' parameterif (!q || typeof q !== 'string') {// This will throw when frontend sends 'query' instead of 'q'throw new Error(`Missing required parameter 'q'. Received parameters: ${Object.keys(req.query).join(', ')}`);}logger.info(logger.fmt`Backend searching for: "${q}"`);// Simple search implementationconst results = await db.select({id: courses.id,title: courses.title,slug: courses.slug,description: courses.description,instructor: users.name,thumbnail: courses.thumbnail,category: courses.category,level: courses.level,duration: courses.duration,price: courses.price,rating: courses.rating,reviewCount: courses.reviewCount,}).from(courses).leftJoin(users, eq(courses.instructorId, users.id)).where(or(ilike(courses.title, `%${q}%`),ilike(courses.description, `%${q}%`))).orderBy(courses.rating).limit(50);// Add search results attributesspan.setAttributes({'search.results_count': results.length,'search.results_found': results.length > 0,'search.query_successful': true,});logger.info(logger.fmt`Backend found ${results.length} results for query: "${q}"`);const responseData = {results,total: results.length,query: q};res.json(responseData);});} catch (error: any) {Sentry.captureException(error, {tags: {operation: 'search.courses.server',query: req.query.q as string || 'undefined',},extra: {queryParameters: req.query,searchQuery: req.query.q,receivedParameters: Object.keys(req.query),requestUrl: req.url,},});logger.error(logger.fmt`Search API Error for query "${req.query.q}": ${error.message}`);throw new Error(error.message);}}); -
Test Search Tracing
Try searching for courses again. Navigate to Explore > Traces in Sentry’s left navigation menu, and search using the
span.description
filter to find your search-related spans using thesearch.courses.server
value. -
Examine the Trace
Clicking into one of the spans will show you the trace waterfall where you can examine the properties of any of the spans submitted.
-
Examine the Logs
Check the Explore > Logs tab for the log entries created by
logger.info
andlogger.error
calls.
Analyzing the Issue
When we explore these traces and logs, we can see an inconsistency - the frontend is sending a query
parameter, but the backend is expecting a q
parameter instead.
Fixing the Issue
The issue is a parameter name mismatch between the frontend and backend! The frontend is sending ?query=
but the server expects ?q=
.
-
Update the Frontend Parameter Name
In the frontend search implementation, replace:
() => fetchApi<any[]>(`/search/courses?query=${encodeURIComponent(query)}`)With:
() => fetchApi<any[]>(`/search/courses?q=${encodeURIComponent(query)}`)This will ensure the frontend sends the parameter name that the backend expects.
Try searching for courses again, and if you navigate to Explore > Traces on left navigation menu, you’ll be able to search using the
span.description
filter and find your search-related spans.
Once this is complete, give search a try again and you should see your results!
