I recall seeing some implementation that did that by having a node table (with whatever metadata you need to assign to each node) and a link table (with a first column as starting node and second column as ending node, with extra columns if the links require extra metadata).
In the easiest graph configuration (a direct acyclic graph or DAG) you can perform traversals by repeatedly querying by first column on the link table. A Djkstra should also be straightforward (assuming a weight column in the link table; you can store the distances either in memory or into a ephemeral table, depending on the graph size magnitude and use case).
More complex graphs may require some conventions (if links are not directional you would need to establish a convention on first and second column, and if more than one link can exist between two nodes then you cannot do a unique together with those two columns and need an extra identifier for each link).
That feels like the most naive approach to your problem. If you need a more complex solution maybe look at the RDF strategies (although those are used as an alternative to relational databases if I remember correctly, so maybe they do not make sense I’m this contex).
Of course it can be done more efficiently… with a non-relational database xD
If you want performance look into real graph databases implementations. Optimizing a graph database over a relational database seems a waste of time IMHO. I may be mistaken.
If you haven’t read this, start here: https://en.wikipedia.org/wiki/Triplestore